模拟和模拟有什么区别?存根?
我读过各种关于测试中模拟与存根的文章,包括 Martin Fowler 的模拟不是存根 ,但还是不明白其中的区别。
I've read various articles about mocking vs stubbing in testing, including Martin Fowler's Mocks Aren't Stubs, but still don't understand the difference.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(30)
前言 对象
有多种定义,但它们并不真实。通用术语是测试替身。该术语包括:虚拟、假、存根、模拟。
参考
Martin Fowler 的文章:
样式
Mocks vs Stubs = 行为测试 vs 状态测试
原理
根据每个测试只测试一件事的原则,一个测试中可能有多个 Stub,但一般只有一个 Mock。
生命周期
使用存根测试生命周期:
使用模拟测试生命周期:
摘要
模拟和存根测试都给出了以下问题的答案:结果是什么?
使用模拟进行测试还感兴趣的是:结果如何已经实现了吗?
Foreword
There are several definitions of objects, that are not real. The general term is test double. This term encompasses: dummy, fake, stub, mock.
Reference
According to Martin Fowler's article:
Style
Mocks vs Stubs = Behavioral testing vs State testing
Principle
According to the principle of Test only one thing per test, there may be several stubs in one test, but generally there is only one mock.
Lifecycle
Test lifecycle with stubs:
Test lifecycle with mocks:
Summary
Both mocks and stubs testing give an answer for the question: What is the result?
Testing with mocks are also interested in: How the result has been achieved?
存根
我认为最大的区别是你已经用预定的行为编写了存根。因此,您将拥有一个实现您出于测试目的而伪造的依赖项(很可能是抽象类或接口)的类,并且这些方法将被设置的响应删除。他们不会做任何花哨的事情,并且您可能已经在测试之外为其编写了存根代码。
模拟
模拟是作为测试的一部分,您必须根据自己的期望进行设置。模拟不是以预定方式设置的,因此您可以在测试中使用代码来执行此操作。在某种程度上,模拟是在运行时确定的,因为设置期望的代码必须在执行任何操作之前运行。
模拟和存根之间的区别
用模拟编写的测试通常遵循
初始化 -> 。设定期望 ->锻炼->验证
模式进行测试。虽然预先编写的存根将遵循initialize ->;锻炼->验证。
模拟和存根之间的相似性
两者的目的都是消除测试类或函数的所有依赖项,以便您的测试更加集中且更简单地尝试证明什么。
Stub
I believe the biggest distinction is that a stub you have already written with predetermined behavior. So you would have a class that implements the dependency (abstract class or interface most likely) you are faking for testing purposes and the methods would just be stubbed out with set responses. They would not do anything fancy and you would have already written the stubbed code for it outside of your test.
Mock
A mock is something that as part of your test you have to setup with your expectations. A mock is not setup in a predetermined way so you have code that does it in your test. Mocks in a way are determined at runtime since the code that sets the expectations has to run before they do anything.
Difference between Mocks and Stubs
Tests written with mocks usually follow an
initialize -> set expectations -> exercise -> verify
pattern to testing. While the pre-written stub would follow aninitialize -> exercise -> verify
.Similarity between Mocks and Stubs
The purpose of both is to eliminate testing all the dependencies of a class or function so your tests are more focused and simpler in what they are trying to prove.
存根是一个简单的伪造对象。它只是确保测试顺利进行。
模拟是一个更智能的存根。您验证您的测试通过了。
A stub is a simple fake object. It just makes sure test runs smoothly.
A mock is a smarter stub. You verify your test passes through it.
在 codeschool.com 课程中,Rails Test for Zombies,他们给出了术语的定义:
Stub
嘲笑
因此,正如肖恩·科本哈弗(Sean Copenhaver)在他的回答中所描述的那样,不同之处在于模拟设定了期望(即做出断言,关于它们是否或如何被调用)。
In the codeschool.com course, Rails Testing for Zombies, they give this definition of the terms:
Stub
Mock
So as Sean Copenhaver described in his answer, the difference is that mocks set expectations (i.e. make assertions, about whether or how they get called).
存根不会让你的测试失败,而模拟却可以。
Stubs don't fail your tests, mock can.
阅读上面的所有解释,让我尝试浓缩一下:
Reading all the explanations above, let me try to condense:
我认为关于这个问题最简单、更清晰的答案来自于Roy Osherove在他的书单元测试的艺术(第85页)中
Stub 和mock 都是假的。
如果您对伪造品做出断言,则意味着您正在使用伪造品作为模拟,如果您仅使用伪造品来运行测试而不对其进行断言,那么您正在使用伪造品作为存根。
I think the simplest and clearer answer about this question is given from Roy Osherove in his book The art of Unit Testing (page 85)
Stub and mock are both fakes.
If you are making assertions against the fake it means you are using the fake as a mock, if you are using the fake only to run the test without assertion over it you are using the fake as a stub.
模拟只是测试行为,确保调用某些方法。
存根是特定对象的可测试版本(本身)。
苹果方式是什么意思?
A Mock is just testing behaviour, making sure certain methods are called.
A Stub is a testable version (per se) of a particular object.
What do you mean an Apple way?
如果将其与调试进行比较:
If you compare it to debugging:
非常清楚和实用:
存根:实现要伪造的类/对象的方法并始终返回您想要的内容的类或对象。
JavaScript 中的示例:
Mock:与存根相同,但它添加了一些在调用方法时“验证”的逻辑,以便您可以确定某些实现正在调用该方法。
正如 @mLevan 所说,想象一下您正在测试用户注册类的示例。调用 Save 后,应调用 SendConfirmationEmail。
一个非常愚蠢的代码示例:
To be very clear and practical:
Stub: A class or object that implements the methods of the class/object to be faked and returns always what you want.
Example in JavaScript:
Mock: The same of stub, but it adds some logic that "verifies" when a method is called so you can be sure some implementation is calling that method.
As @mLevan says imagine as an example that you're testing a user registration class. After calling Save, it should call SendConfirmationEmail.
A very stupid code Example:
这张幻灯片很好地解释了主要差异。
*摘自华盛顿大学 CSE 403 第 16 讲(幻灯片由“Marty Stepp”创建)
This slide explain the main differences very good.
*From CSE 403 Lecture 16 , University of Washington (slide created by "Marty Stepp")
使用心理模型确实帮助我理解了这一点,而不是所有没有完全“融入”的解释和文章。
想象一下你的孩子桌子上有一个玻璃盘,他开始玩它。现在,你担心它会破裂。所以,你给他一个塑料盘子。这将是一个模拟(相同的行为,相同的接口,“更软”的实现)。
现在,假设你没有塑料替代品,所以你解释说“如果你继续玩它,它就会破裂!”。这是一个存根,您提前提供了预定义的状态。
Dummy 可能是他甚至没有使用过的叉子......而 Spy 可能是提供与您已经使用过的相同的解释。
Using a mental model really helped me understand this, rather than all of the explanations and articles, that didn't quite "sink in".
Imagine your kid has a glass plate on the table and he starts playing with it. Now, you're afraid it will break. So, you give him a plastic plate instead. That would be a Mock (same behavior, same interface, "softer" implementation).
Now, say you don't have the plastic replacement, so you explain "If you continue playing with it, it will break!". That's a Stub, you provided a predefined state in advance.
A Dummy would be the fork he didn't even use... and a Spy could be something like providing the same explanation you already used that worked.
让我们看看测试替身:
存根:存根是一个保存预定义数据并在测试期间使用它来应答呼叫的对象。 如:需要从数据库抓取一些数据来响应方法调用的对象。
模拟:模拟是注册它们收到的调用的对象。
在测试断言中,我们可以在 Mock 上验证是否执行了所有预期操作。 如:调用电子邮件发送服务的功能。
有关更多信息,请查看此。
let see Test Doubles:
Stub: Stub is an object that holds predefined data and uses it to answer calls during tests. Such as: an object that needs to grab some data from the database to respond to a method call.
Mocks: Mocks are objects that register calls they receive.
In test assertion, we can verify on Mocks that all expected actions were performed. Such as: a functionality that calls e-mail sending service.
for more just check this.
我喜欢 Roy Osherove 在他的演讲理解模拟对象中提出的解释:
I like the explanation put out by Roy Osherove in his talk Understanding Mock Objects:
我认为他们之间最重要的区别是他们的意图。
让我尝试用为什么存根与为什么模拟来解释它
假设我正在为我的Mac Twitter客户端的公共时间线控制器编写测试代码
这里是测试示例代码
通过编写mock,您可以通过验证是否满足期望来发现对象协作关系,而stub仅模拟对象的行为。
如果您想了解有关模拟的更多信息,我建议您阅读这篇文章:http://jmock.org/oopsla2004.pdf
I think the most important difference between them is their intentions.
Let me try to explain it in WHY stub vs. WHY mock
Suppose I'm writing test code for my mac twitter client's public timeline controller
Here is test sample code
By writing mock, you discover the objects collaboration relationship by verifying the expectation are met, while stub only simulate the object's behavior.
I suggest to read this article if you're trying to know more about mocks: http://jmock.org/oopsla2004.pdf
存根
存根是一个对象,用于伪造具有预编程行为的方法。您可能希望使用此方法而不是现有方法,以避免不必要的副作用(例如,存根可能会进行虚假的提取调用,返回预编程的响应,而不实际向服务器发出请求)。
模拟
模拟是一个对象,用于伪造具有预编程行为以及预编程期望的方法。如果这些期望没有得到满足,那么模拟将导致测试失败(例如,模拟可能会进行虚假的获取调用,返回预编程的响应,而不实际向服务器发出预期的请求例如,第一个参数为
"http://localhost:3008/"
,否则测试将失败。)差异
与模拟不同,存根没有可能导致测试失败的预编程期望。
Stub
A stub is an object used to fake a method that has pre-programmed behavior. You may want to use this instead of an existing method in order to avoid unwanted side-effects (e.g. a stub could make a fake fetch call that returns a pre-programmed response without actually making a request to a server).
Mock
A mock is an object used to fake a method that has pre-programmed behavior as well as pre-programmed expectations. If these expectations are not met then the mock will cause the test to fail (e.g. a mock could make a fake fetch call that returns a pre-programmed response without actually making a request to a server which would expect e.g. the first argument to be
"http://localhost:3008/"
otherwise the test would fail.)Difference
Unlike mocks, stubs do not have pre-programmed expectations that could fail your test.
我正在阅读单元测试的艺术,并偶然发现了以下定义:
I was reading The Art of Unit Testing, and stumbled upon the following definition:
模拟:帮助模拟和检查结果交互。这些相互作用
SUT 对其依赖项进行调用以更改其状态。
存根:帮助模拟传入的交互。这些相互作用被称为
SUT 通过其依赖关系来获取输入数据。
来源:单元测试原则、实践和模式 - Manning
Mocks: help to emulate and examine outcoming interactions. These interactions
are calls the SUT makes to its dependencies to change their state.
Stubs: help to emulate incoming interactions. These interactions are calls the
SUT makes to its dependencies to get input data.
source : Unit Testing Principles, Practices, and Patterns - Manning
他使用的通用术语是测试替身(想想特技替身)。测试替身是一个通用术语,适用于出于测试目的而替换生产对象的任何情况。 Gerard 列出了各种类型的 double:
来源
The generic term he uses is a Test Double (think stunt double). Test Double is a generic term for any case where you replace a production object for testing purposes. There are various kinds of double that Gerard lists:
Source
存根
存根是一个保存预定义数据并在测试期间使用它来应答调用的对象。当您不能或不想涉及会用真实数据回答或具有不良副作用的对象时,可以使用它。
一个示例可以是需要从数据库获取一些数据以响应方法调用的对象。我们引入了一个存根并定义了应该返回哪些数据,而不是真正的对象。
存根示例:
您无需从成绩册商店调用数据库来获取真实的学生成绩,而是预先配置存根并将返回成绩。您定义了足够的数据来测试平均计算算法。
Mock
模拟是注册它们收到的调用的对象。在测试断言中,您可以在模拟上验证是否执行了所有预期操作。当您不想调用生产代码或者没有简单的方法来验证预期代码是否已执行时,您可以使用模拟。没有返回值,也没有简单的方法来检查系统状态更改。一个示例可以是调用电子邮件发送服务的功能。
您不希望每次运行测试时都发送电子邮件。此外,在测试中验证是否发送了正确的电子邮件并不容易。您唯一能做的就是验证我们测试中所执行的功能的输出。在其他世界中,验证是否调用了电子邮件发送服务。
模拟示例:
您不想关闭真实的门来测试安全方法是否有效,对吗?相反,您将门和窗模拟对象放置在测试代码中。
非常感谢 Michał Lipski 的好文章。进一步阅读:
测试替身 – Martin Fowler https://martinfowler.com/bliki/TestDouble.html
测试替身 – xUnit 模式 http://xunitpatterns.com/Test%20Double.html
模拟不是存根 – Martin Fowler https://martinfowler.com/articles/mocksArentStubs.html
命令查询分离 – Martin Fowler https://martinfowler.com/bliki/CommandQuerySeparation.html
Stub
A stub is an object that holds predefined data and uses it to answer calls during tests. It is used when you can’t or don’t want to involve objects that would answer with real data or have undesirable side effects.
An example can be an object that needs to grab some data from the database to respond to a method call. Instead of the real object, we introduced a stub and defined what data should be returned.
example of Stub:
Instead of calling database from Gradebook store to get real students grades, you preconfigure stub with grades that will be returned. You define just enough data to test average calculation algorithm.
Mock
Mocks are objects that register calls they receive. In test assertion you can verify on Mocks that all expected actions were performed. You use mocks when you don’t want to invoke production code or when there is no easy way to verify, that intended code was executed. There is no return value and no easy way to check system state change. An example can be a functionality that calls e-mail sending service.
You don’t want to send e-mails each time you run a test. Moreover, it is not easy to verify in tests that a right email was send. Only thing you can do is to verify the outputs of the functionality that is exercised in our test. In other worlds, verify that the e-mail sending service was called.
Example of Mock:
You don’t want to close real doors to test that security method is working, right? Instead, you place door and window mocks objects in the test code.
Thanks a lot to Michał Lipski for his good article. For further reading:
Test Double – Martin Fowler https://martinfowler.com/bliki/TestDouble.html
Test Double – xUnit Patterns http://xunitpatterns.com/Test%20Double.html
Mocks Aren’t Stubs – Martin Fowler https://martinfowler.com/articles/mocksArentStubs.html
Command Query Separation – Martin Fowler https://martinfowler.com/bliki/CommandQuerySeparation.html
假货是一个通用术语,可用于描述存根
或模拟对象(手写或其他方式),因为它们看起来都像
真实的物体。
假货是存根还是模拟取决于它的使用方式
当前的测试。如果它用于检查交互(断言反对),那么它是
模拟对象。否则,它就是一个存根。
Fakes 确保测试顺利进行。这意味着未来测试的读者将了解伪造对象的行为,而无需阅读其源代码(无需依赖外部资源)。
试运行顺利是什么意思?
例如在下面的代码中:
您想要测试 mailService.SendEMail() 方法,为此您需要在测试方法中模拟异常,因此您只需要创建一个 Fake Stub errorService 类来模拟该结果,那么您的测试代码将能够测试 mailService.SendEMail() 方法。如您所见,您需要模拟来自另一个外部依赖项 ErrorService 类的结果。
A fake is a generic term that can be used to describe either a stub
or a mock object (handwritten or otherwise), because they both look like the
real object.
Whether a fake is a stub or a mock depends on how it’s used in
the current test. If it’s used to check an interaction (asserted against), it’s a
mock object. Otherwise, it’s a stub.
Fakes makes sure test runs smoothly. It means that reader of your future test will understand what will be the behavior of the fake object, without needing to read its source code (without needing to depend on external resource).
What does test run smoothly mean?
Forexample in below code:
You want to test mailService.SendEMail() method, to do that you need to simulate an Exception in you test method, so you just need to create a Fake Stub errorService class to simulate that result, then your test code will be able to test mailService.SendEMail() method. As you see you need to simulate a result which is from an another External Dependency ErrorService class.
来自 jMock 开发人员的论文模拟角色,而不是对象:
因此,主要区别在于:
总而言之,同时也试图消除 Fowler 的文章标题中的混乱:模拟是存根,但它们不仅仅是存根。
Right from the paper Mock Roles, not Objects, by the developers of jMock :
So, the main differences are:
To sum up, while also trying to disperse the confusion from Fowler's article title: mocks are stubs, but they are not only stubs.
我看到了 UncleBob The Little Mocker 写的这篇有趣的文章。它以非常容易理解的方式解释了所有术语,因此对初学者很有用。马丁·福勒斯的文章很难读,特别是对于像我这样的初学者来说。
I came across this interesting article by UncleBob The Little Mocker. It explains all the terminology in a very easy to understand manner, so its useful for beginners. Martin Fowlers article is a hard read especially for beginners like me.
那里有很多有效的答案,但我认为值得一提鲍勃叔叔的这个表格:
https://8thlight.com/blog/uncle-bob/2014 /05/14/TheLittleMocker.html
有史以来最好的解释与示例!
a lot of valid answers up there but I think worth to mention this form uncle bob:
https://8thlight.com/blog/uncle-bob/2014/05/14/TheLittleMocker.html
the best explanation ever with examples!
模拟既是一个技术对象,又是一个功能对象。
模拟是技术性的。由于字节代码生成,它确实是由模拟库创建的(EasyMock、JMockit 和最近的 Mockito 因这些而闻名)。
模拟实现是生成的,我们可以检测它在调用方法时返回特定值,但也可以进行其他一些操作,例如验证模拟方法是否是使用某些特定参数(严格检查)或任何参数(无严格检查)调用。
实例化模拟:
记录行为:
验证调用:
这些显然不是实例化/覆盖 Foo 类/行为的自然方式。这就是为什么我提到技术方面。
但是模拟也具有功能性,因为它是我们需要与 SUT 隔离的类的实例。通过记录其行为,我们可以在 SUT 中使用它,就像使用存根一样。
存根只是一个功能对象:它是我们需要与 SUT 隔离的类的实例,仅此而已。
这意味着我们的单元测试期间所需的存根类和所有行为固定装置都必须明确定义。
例如,要存根
hello()
需要子类化Foo
类(或实现其接口)并重写hello()
:如果另一个测试场景需要另一个值返回,我们可能需要定义一个通用方法来设置返回:
其他场景:如果我有一个副作用方法(无返回)并且我会检查该方法是否被调用,我应该可能在存根类中添加了一个布尔值或计数器来计算该方法被调用的次数。
结论
存根通常需要大量开销/代码来为单元测试编写。由于提供了开箱即用的记录/验证功能,模拟可以防止什么。
这就是为什么现在随着优秀的模拟库的出现,存根方法在实践中很少使用。
关于 Martin Fowler 的文章:当我使用模拟并且避免存根时,我不认为自己是一个“模拟主义者”程序员。
但当真正需要时我会使用模拟(烦人的依赖项),并且当我测试具有依赖项的类时,我喜欢测试切片和小型集成测试,而模拟将是一种开销。
A mock is both a technical and a functional object.
The mock is technical. It is indeed created by a mocking library (EasyMock, JMockit and more recently Mockito are known for these) thanks to byte code generation.
The mock implementation is generated in a way where we could instrument it to return a specific value when a method is invoked but also some other things such as verifying that a mock method was invoked with some specific parameters (strict check) or whatever the parameters (no strict check).
Instantiating a mock :
Recording a behavior :
Verifying an invocation :
These are clearly not the natural way to instantiate/override the Foo class/behavior. That's why I refer to a technical aspect.
But the mock is also functional because it is an instance of the class we need to isolate from the SUT. And with recorded behaviors on it, we could use it in the SUT in the same way than we would do with a stub.
The stub is just a functional object : that is an instance of the class we need to isolate from the SUT and that's all.
That means that both the stub class and all behaviors fixtures needed during our unit tests have to be defined explicitly.
For example to stub
hello()
would need to subclass theFoo
class (or implements its interface it has it) and to overridehello()
:If another test scenario requires another value return, we would probably need to define a generic way to set the return :
Other scenario : if I had a side effect method (no return) and I would check that that method was invoked, I should probably have added a boolean or a counter in the stub class to count how many times the method was invoked.
Conclusion
The stub requires often much overhead/code to write for your unit test. What mock prevents thanks to providing recording/verifying features out of the box.
That's why nowadays, the stub approach is rarely used in practice with the advent of excellent mock libraries.
About the Martin Fowler Article : I don't think to be a "mockist" programmer while I use mocks and I avoid stubs.
But I use mock when it is really required (annoying dependencies) and I favor test slicing and mini-integration tests when I test a class with dependencies which mocking would be an overhead.
Stubs 和 Mocks 都会覆盖外部依赖,但区别在于
Stubs -> 测试数据
模拟 -> 测试行为
引用 Fowler 的模拟不是存根 文章:
这显然说明了区别。
假/虚拟 -> 不测试(只需使用空方法覆盖功能,例如替换
Logger
以避免测试时出现任何日志记录噪音)Both Stubs and Mocks override external dependencies but the difference is
Stubs -> To Test Data
Mocks -> To Test Behavior
A quote from Fowler's Mocks Aren't Stubs article:
This obviously tells the difference.
Fake/Dummy -> Test nothing (just override functionality with empty methods, eg replace
Logger
to avoid any logging noise while testing)加上有用的答案,使用 Mocks 比 Subs 的最强大的点之一
如果协作者[主代码依赖于它]不在我们的控制之下(例如来自第三方库),
在这种情况下,存根比模拟更难编写。
Plus useful answers, One of the most powerful point of using Mocks than Subs
If the collaborator [which the main code depend on it] is not under our control (e.g. from a third-party library),
In this case, stub is more difficult to write rather than mock.
存根帮助我们运行测试。如何?它提供有助于运行测试的值。这些值本身并不真实,我们创建这些值只是为了运行测试。例如,我们创建一个 HashMap 来为我们提供与数据库表中的值类似的值。因此,我们不是直接与数据库交互,而是与 Hashmap 交互。
Mock 是一个运行测试的假对象。我们把断言放在哪里。
Stub helps us to run test. How? It gives values which helps to run test. These values are itself not real and we created these values just to run the test. For example we create a HashMap to give us values which are similar to values in database table. So instead of directly interacting with database we interact with Hashmap.
Mock is an fake object which runs the test. where we put assert.
请参阅下面使用 C# 和 Moq 框架的模拟与存根的示例。 Moq 没有 Stub 的特殊关键字,但您也可以使用 Mock 对象来创建存根。
See below example of mocks vs stubs using C# and Moq framework. Moq doesn't have a special keyword for Stub but you can use Mock object to create stubs too.