我已经阅读了各种关于模拟和测试中存根的文章,包括Martin Fowler's Mocks Aren't Stubs,但仍然不明白其中的区别。
存根
我相信最大的区别是你已经用预定的行为编写了一个存根。所以你会有一个实现依赖的类(最有可能的抽象类或接口),你为了测试目的而伪装,这些方法只是用set响应来删除。他们不会做任何花哨的事情,你会在测试之外编写它的存根代码。
嘲笑
模拟是你的测试的一部分,你必须设置你的期望。模拟不是以预定方式设置的,因此您可以在测试中使用代码。某种方式的模拟是在运行时确定的,因为设置期望的代码必须在它们执行任何操作之前运行。
模拟和存根之间的区别
用模拟编写的测试通常遵循initialize -> set expectations -> exercise -> verify
模式进行测试。虽然预先编写的存根将遵循initialize -> exercise -> verify
。
模拟与存根之间的相似性
两者的目的是消除测试类或函数的所有依赖关系,以便您的测试更加集中,更简单,他们试图证明。
如果将它与调试进行比较:
Stub就像确保方法返回正确的值一样
模拟实际上就像进入方法并确保内部的所有内容都正确,然后返回正确的值。
我认为他们之间最重要的区别是他们的意图。
让我试着在WHY stub和WHY mock中解释它
假设我正在为我的mac twitter客户端的公共时间线控制器编写测试代码
这是测试示例代码
twitter_api.stub(:public_timeline).and_return(public_timeline_array)
client_ui.should_receive(:insert_timeline_above).with(public_timeline_array)
controller.refresh_public_timeline
通过编写mock,您可以通过验证期望得到满足来发现对象协作关系,而stub只模拟对象的行为。
如果你想了解更多关于模拟的信息,我建议阅读这篇文章:http://jmock.org/oopsla2004.pdf
要非常清楚和实用:
Stub:一个实现要伪造的类/对象的方法的类或对象,它总是返回你想要的。
JavaScript中的示例:
var Stub = {
method_a: function(param_a, param_b){
return 'This is an static result';
}
}
模拟:存根相同,但它添加了一些逻辑,在调用方法时“验证”,因此您可以确定某些实现正在调用该方法。
正如@mLevan所说,想象一下您正在测试用户注册类。调用Save后,应该调用SendConfirmationEmail。
一个非常愚蠢的代码示例:
var Mock = {
calls: {
method_a: 0
}
method_a: function(param_a, param_b){
this.method_a++;
console.log('Mock.method_a its been called!');
}
}
使用心理模型确实帮助我理解了这一点,而不是所有的解释和文章,并没有完全“沉入”。
想象一下,你的孩子桌子上有一个玻璃盘,他开始玩它。现在,你担心它会破裂。所以,你给他一块塑料板。这将是一个模拟(相同的行为,相同的界面,“更软”的实现)。
现在,假设你没有塑料更换,所以你解释“如果你继续玩它,它会破坏!”。这是一个Stub,你事先提供了一个预定义的状态。
Dummy将是他甚至没有使用的叉子......而间谍可能就像提供你已经使用的相同解释一样有效。
我喜欢Roy Osherove Qazxsw Poi提出的解释。
创建的每个类或对象都是假的。如果您验证对它的呼叫,它是一个模拟。否则它是一个存根。
伪是一个通用术语,可用于描述存根或模拟对象(手写或其他),因为它们看起来都像真实对象。
假的是存根还是模拟取决于它在当前测试中的使用方式。如果它用于检查交互(断言),则它是一个模拟对象。否则,它是一个存根。
假货确保测试顺利进行。这意味着未来测试的读者将理解虚假对象的行为,而无需阅读其源代码(无需依赖外部资源)。
测试运行顺畅意味着什么? 例如下面的代码:
[video link]
你想测试mailService.SendEMail()方法,为了做到这一点你需要在你的测试方法中模拟一个Exception,所以你只需要创建一个Fake Stub errorService类来模拟那个结果,然后你的测试代码就可以测试了mailService.SendEMail()方法。如您所见,您需要模拟来自另一个外部依赖性ErrorService类的结果。
让我看看测试双打:
public void Analyze(string filename)
{
if(filename.Length<8)
{
try
{
errorService.LogError("long file entered named:" + filename);
}
catch (Exception e)
{
mailService.SendEMail("[email protected]", "ErrorOnWebService", "someerror");
}
}
}
。从纸张this,由jMock的开发人员:
存根是生成代码的虚拟实现,可返回固定结果。模拟对象充当存根,但也包括用于检测目标对象与其邻居的交互的断言。
所以,主要的区别是:
总而言之,同时也试图驱散Mock Roles, not Objects标题的混乱:模拟是存根,但它们不仅仅是存根。
对象有几种定义,不是真实的。一般术语是测试双倍。这个术语包括:虚拟,假,存根,模拟。
- 虚拟对象传递但从未实际使用过。通常它们仅用于填充参数列表。
- 假对象实际上有工作实现,但通常需要一些使它们不适合生产的快捷方式(内存数据库就是一个很好的例子)。
- 存根提供了在测试期间进行的调用的固定答案,通常不会对测试中编程的任何内容做出任何响应。存根还可以记录有关呼叫的信息,例如记住它'发送'的消息的电子邮件网关存根,或者可能只记录它'发送'的消息。
- 模拟是我们在这里所讨论的:预编程的对象具有预期,形成了预期接收的调用的规范。
Mocks vs Stubs =行为测试与状态测试
根据每次测试只测试一件事的原则,在一次测试中可能有几个存根,但通常只有一个模拟。
使用存根测试生命周期:
使用模拟测试生命周期:
模拟和存根测试都给出了问题的答案:结果是什么?
使用模拟测试也对以下方面感兴趣:结果如何实现?
请参阅下面使用C#和Moq框架的mocks vs stubs示例。 Moq没有Stub的特殊关键字,但您也可以使用Mock对象创建存根。
Fowler's article
我从UncleBob namespace UnitTestProject2
{
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
[TestClass]
public class UnitTest1
{
/// <summary>
/// Test using Mock to Verify that GetNameWithPrefix method calls Repository GetName method "once" when Id is greater than Zero
/// </summary>
[TestMethod]
public void GetNameWithPrefix_IdIsTwelve_GetNameCalledOnce()
{
// Arrange
var mockEntityRepository = new Mock<IEntityRepository>();
mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>()));
var entity = new EntityClass(mockEntityRepository.Object);
// Act
var name = entity.GetNameWithPrefix(12);
// Assert
mockEntityRepository.Verify(m => m.GetName(It.IsAny<int>()), Times.Once);
}
/// <summary>
/// Test using Mock to Verify that GetNameWithPrefix method doesn't call Repository GetName method when Id is Zero
/// </summary>
[TestMethod]
public void GetNameWithPrefix_IdIsZero_GetNameNeverCalled()
{
// Arrange
var mockEntityRepository = new Mock<IEntityRepository>();
mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>()));
var entity = new EntityClass(mockEntityRepository.Object);
// Act
var name = entity.GetNameWithPrefix(0);
// Assert
mockEntityRepository.Verify(m => m.GetName(It.IsAny<int>()), Times.Never);
}
/// <summary>
/// Test using Stub to Verify that GetNameWithPrefix method returns Name with a Prefix
/// </summary>
[TestMethod]
public void GetNameWithPrefix_IdIsTwelve_ReturnsNameWithPrefix()
{
// Arrange
var stubEntityRepository = new Mock<IEntityRepository>();
stubEntityRepository.Setup(m => m.GetName(It.IsAny<int>()))
.Returns("Stub");
const string EXPECTED_NAME_WITH_PREFIX = "Mr. Stub";
var entity = new EntityClass(stubEntityRepository.Object);
// Act
var name = entity.GetNameWithPrefix(12);
// Assert
Assert.AreEqual(EXPECTED_NAME_WITH_PREFIX, name);
}
}
public class EntityClass
{
private IEntityRepository _entityRepository;
public EntityClass(IEntityRepository entityRepository)
{
this._entityRepository = entityRepository;
}
public string Name { get; set; }
public string GetNameWithPrefix(int id)
{
string name = string.Empty;
if (id > 0)
{
name = this._entityRepository.GetName(id);
}
return "Mr. " + name;
}
}
public interface IEntityRepository
{
string GetName(int id);
}
public class EntityRepository:IEntityRepository
{
public string GetName(int id)
{
// Code to connect to DB and get name based on Id
return "NameFromDb";
}
}
}
那里看到了这篇有趣的文章。它以一种非常容易理解的方式解释了所有术语,因此对初学者很有用。 Martin Fowlers的文章特别适合像我这样的初学者。
Stub和Mock测试观点:
Stub帮助我们进行测试。怎么样?它给出了有助于运行测试的值。这些值本身并不真实,我们创建这些值只是为了运行测试。例如,我们创建一个HashMap,为我们提供与数据库表中的值类似的值。因此,我们不是直接与数据库交互,而是与Hashmap进行交互。
模拟是一个运行测试的假对象。我们把断言放在哪里。
我在答案中使用了python示例来说明差异。
Stub - Stubbing是一种软件开发技术,用于在开发生命周期的早期实现类的方法。它们通常用作占位符,用于实现已知接口,其中接口已完成或已知,但实现尚未知晓或最终确定。您从存根开始,这只是意味着您只需要编写函数的定义并保留实际代码以供日后使用。优点是你不会忘记方法,你可以在代码中看到它时继续考虑你的设计。您还可以让存根返回静态响应,以便响应可以立即被代码的其他部分使用。存根对象提供有效的响应,但无论您传入什么输入,它都是静态的,您将始终获得相同的响应:
here
模拟对象用于模拟测试用例,它们验证在这些对象上调用某些方法。模拟对象是模拟对象,以受控方式模拟真实对象的行为。您通常会创建一个模拟对象来测试其他对象的行为。模拟让我们模拟单元测试不可用或太笨重的资源。
没有module.朋友:
class Foo(object):
def bar1(self):
pass
def bar2(self):
#or ...
raise NotImplementedError
def bar3(self):
#or return dummy data
return "Dummy Data"
test.朋友:
import os
import os.path
def rm(filename):
if os.path.isfile(filename):
os.remove(filename)
这是一个非常基本的例子,只运行rm并断言它被调用的参数。您可以将mock与对象一起使用,而不仅仅是此处所示的函数,您还可以返回一个值,以便可以使用模拟对象替换存根进行测试。
更多关于from mymodule import rm
import mock
import unittest
class RmTestCase(unittest.TestCase):
@mock.patch('mymodule.os')
def test_rm(self, mock_os):
rm("any path")
# test that rm called os.remove with the right parameters
mock_os.remove.assert_called_with("any path")
if __name__ == '__main__':
unittest.main()
,请注意python 2.x中的mock不包含在unittest中,但它是一个可下载的模块,可以通过pip(pip install mock)下载。
我还读过Roy Osherove的“单元测试艺术”,我认为如果使用Python和Python的例子编写类似的书,那将会很棒。如果有人知道这样的书,请分享。干杯:)
存根是为测试目的而构建的虚假对象。模拟是一个存根,它记录预期的调用是否有效发生。
存根是一个空函数,用于在测试期间避免未处理的异常:
unittest.mock
模拟是一种人工函数,用于在测试期间避免操作系统,环境或硬件依赖:
function foo(){}
在断言和状态方面:
参考
function foo(bar){ window = this; return window.toString(bar); }
那里有很多有效的答案,但我觉得值得一提的是这个形式的叔叔鲍勃:Test Doubles: Fakes, Mocks and Stubs
有例子的最佳解释!
Stub是一个实现组件接口的对象,但不是返回组件在调用时返回的内容,而是可以将存根配置为返回适合测试的值。使用存根,单元测试可以测试单元是否可以处理来自其协作者的各种返回值。在单元测试中使用存根而不是真正的协作者可以表达如下:
单元测试 - >存根
单元测试 - >单元 - >存根
单元测试断言结果和单元状态
首先,单元测试创建存根并配置其返回值。然后单元测试创建单元并在其上设置存根。现在单元测试调用单元,而单元又调用存根。最后,单元测试会对单元上方法调用的结果进行断言。
模拟就像一个存根,只有它还有一些方法可以确定模拟调用的方法。因此,使用模拟可以测试单元是否可以正确处理各种返回值,以及单元是否正确使用协作者。例如,您无法通过dao对象返回的值查看是否使用Statement或PreparedStatement从数据库中读取数据。你也不能看到在返回值之前是否调用了connection.close()方法。这可以通过模拟实现。换句话说,模拟可以测试单元与协作者的完整交互。不只是返回单位使用的值的协作者方法。在单元测试中使用模拟可以表达如下:
单元测试 - >模拟
单元测试 - >单元 - >模拟
单元测试断言单元的结果和状态
单元测试断言在mock上调用的方法
更多细节>> https://8thlight.com/blog/uncle-bob/2014/05/14/TheLittleMocker.html
存根是一个简单的假对象。它只是确保测试顺利进行。 模拟是一个更聪明的存根。您验证测试是否通过它。
以下是我的理解......
以下是对每个示例的描述,然后是真实世界的示例。
API
。
示例:如果您正在测试一个类的方法,该类在构造函数中需要许多强制参数而对测试没有影响,那么您可以创建虚拟对象以创建类的新实例。in-memory
集合。state-based
。
示例:您的测试类依赖于Calculate()
方法需要5分钟才能完成。您可以使用返回硬编码值的存根替换其实际实现,而不是等待5分钟;只占一小部分时间。Stub
但是interaction-based
而不是基于状态。这意味着你不希望Mock
返回一些值,而是假设方法调用的特定顺序。
示例:您正在测试用户注册类。在调用Save
之后,它应该调用SendConfirmationEmail
。Stubs
和Mocks
实际上是Mock
的子类型,它们都与测试实现交换实际实现,但出于不同的具体原因。
在codeschool.com课程中,Rails Testing for Zombies,他们给出了这些术语的定义:
存根
用于使用返回指定结果的代码替换方法。
嘲笑
一个断言,断言该方法被调用。
正如Sean Copenhaver在他的回答中所描述的那样,区别在于模拟设定了期望(即做出断言,关于是否或如何被调用)。
存根不会使您的测试失败,模拟可以。
我认为关于这个问题的最简单和更清晰的答案来自Roy Osherove在他的书The art of Unit Testing(第85页)
告诉我们处理存根的最简单方法是注意存根永远不会使测试失败。断言测试用途总是针对被测试的类。
另一方面,测试将使用模拟对象来验证测试是否失败。 [...]
同样,模拟对象是我们用来查看测试是否失败的对象。
这意味着如果你正在对假冒进行断言,这意味着你使用假冒作为模拟,如果你只使用假冒运行测试而没有断言,你使用假冒作为存根。
阅读上面的所有解释,让我试着浓缩:
模拟只是测试行为,确保调用某些方法。 Stub是特定对象的可测试版本(本身)。
你是什么意思Apple方式?