模拟和模拟有什么区别?存根?

发布于 2024-09-14 01:58:50 字数 149 浏览 10 评论 0原文

我读过各种关于测试中模拟与存根的文章,包括 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

久伴你 2024-09-21 01:58:50

前言 对象

有多种定义,但它们并不真实。通用术语是测试替身。该术语包括:虚拟存根模拟

参考

Martin Fowler 的文章

  • 虚拟对象被传递但从未实际使用。通常它们仅用于填充参数列表。
  • 对象实际上具有有效的实现,但通常会采取一些捷径,这使得它们不适合生产(内存数据库就是一个很好的例子)。
  • 存根为测试期间拨打的电话提供预设答案,通常不会对测试编程之外的任何内容做出响应。存根还可以记录有关调用的信息,例如记住它“发送”的消息的电子邮件网关存根,或者可能只记录它“发送”的消息数量。
  • 模拟就是我们在这里讨论的内容:预先编程的对象,形成了它们预期接收的调用的规范。

样式

Mocks vs Stubs = 行为测试 vs 状态测试

原理

根据每个测试只测试一件事的原则,一个测试中可能有多个 Stub,但一般只有一个 Mock。

生命周期

使用存根测试生命周期:

  1. 设置 - 准备正在测试的对象及其存根协作者。
  2. 练习 - 测试功能。
  3. 验证状态 - 使用断言来检查对象的状态。
  4. 拆卸 - 清理资源。

使用模拟测试生命周期:

  1. 设置数据 - 准备正在测试的对象。
  2. 设置期望 - 在主要对象使用的模拟中准备期望。
  3. 练习 - 测试功能。
  4. 验证期望 - 验证模拟中是否调用了正确的方法。
  5. 验证状态 - 使用断言来检查对象的状态。
  6. 拆卸 - 清理资源。

摘要

模拟和存根测试都给出了以下问题的答案:结果是什么?

使用模拟进行测试还感兴趣的是:结果如何已经实现了吗?

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:

  • Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
  • Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example).
  • Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test. Stubs may also record information about calls, such as an email gateway stub that remembers the messages it 'sent', or maybe only how many messages it 'sent'.
  • Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.

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:

  1. Setup - Prepare object that is being tested and its stubs collaborators.
  2. Exercise - Test the functionality.
  3. Verify state - Use asserts to check object's state.
  4. Teardown - Clean up resources.

Test lifecycle with mocks:

  1. Setup data - Prepare object that is being tested.
  2. Setup expectations - Prepare expectations in mock that is being used by primary object.
  3. Exercise - Test the functionality.
  4. Verify expectations - Verify that correct methods has been invoked in mock.
  5. Verify state - Use asserts to check object's state.
  6. Teardown - Clean up resources.

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?

逐鹿 2024-09-21 01:58:50

存根

我认为最大的区别是你已经用预定的行为编写了存根。因此,您将拥有一个实现您出于测试目的而伪造的依赖项(很可能是抽象类或接口)的类,并且这些方法将被设置的响应删除。他们不会做任何花哨的事情,并且您可能已经在测试之外为其编写了存根代码。

模拟

模拟是作为测试的一部分,您必须根据自己的期望进行设置。模拟不是以预定方式设置的,因此您可以在测试中使用代码来执行此操作。在某种程度上,模拟是在运行时确定的,因为设置期望的代码必须在执行任何操作之前运行。

模拟和存根之间的区别

用模拟编写的测试通常遵循初始化 -> 。设定期望 ->锻炼->验证 模式进行测试。虽然预先编写的存根将遵循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 an initialize -> 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.

像你 2024-09-21 01:58:50

存根是一个简单的伪造对象。它只是确保测试顺利进行。
模拟是一个更智能的存根。您验证您的测试通过了。

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.

过气美图社 2024-09-21 01:58:50

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

For replacing a method with code that returns a specified result.

Mock

A stub with an assertion that the method gets called.

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).

落叶缤纷 2024-09-21 01:58:50

存根不会让你的测试失败,而模拟却可以。

Stubs don't fail your tests, mock can.

千寻… 2024-09-21 01:58:50

阅读上面的所有解释,让我尝试浓缩一下:

  • 存根:一段让测试运行的虚拟代码,但你不关心它会发生什么。替代实际工作代码。
  • 模拟:作为测试的一部分,您验证是否正确调用了一段虚拟代码。替代实际工作代码。
  • 间谍:一段虚拟代码,用于拦截并验证对真实工作代码的某些调用,从而避免替换所有真实代码。

Reading all the explanations above, let me try to condense:

  • Stub: a dummy piece of code that lets the test run, but you don't care what happens to it. Substitutes for real working code.
  • Mock: a dummy piece of code that you verify is called correctly as part of the test. Substitutes for real working code.
  • Spy: a dummy piece of code that intercepts and verifies some calls to real working code, avoiding the need to substitute all the real code.
苏璃陌 2024-09-21 01:58:50

我认为关于这个问题最简单、更清晰的答案来自于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)

The easiest way to tell we’re dealing with a stub is to notice that the stub can never fail the test. The asserts the test uses are always against
the class under test.

On the other hand, the test will use a mock object to verify whether the
test failed or not. [...]

Again, the mock object is the object we use to see if the test failed or not.

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.

烛影斜 2024-09-21 01:58:50

模拟只是测试行为,确保调用某些方法。
存根是特定对象的可测试版本(本身)。

苹果方式是什么意思?

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?

美胚控场 2024-09-21 01:58:50

如果将其与调试进行比较:

存根就像确保方法返回正确的值

Mock 就像实际上进入方法并在返回正确的值之前确保内部的所有内容都是正确的。

If you compare it to debugging:

Stub is like making sure a method returns the correct value

Mock is like actually stepping into the method and making sure everything inside is correct before returning the correct value.

在梵高的星空下 2024-09-21 01:58:50

非常清楚和实用:

存根:实现要伪造的类/对象的方法并始终返回您想要的内容的类或对象。

JavaScript 中的示例:

var Stub = {
   method_a: function(param_a, param_b){
      return 'This is an static result';
   }
}

Mock:与存根相同,但它添加了一些在调用方法时“验证”的逻辑,以便您可以确定某些实现正在调用该方法。

正如 @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!');
   }
}

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:

var Stub = {
   method_a: function(param_a, param_b){
      return 'This is an static result';
   }
}

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:

var Mock = {
   calls: {
      method_a: 0
   }

   method_a: function(param_a, param_b){
     this.method_a++; 
     console.log('Mock.method_a its been called!');
   }
}
哭了丶谁疼 2024-09-21 01:58:50

这张幻灯片很好地解释了主要差异。

输入图像描述这里

*摘自华盛顿大学 CSE 403 第 16 讲(幻灯片由“Marty Stepp”创建)

This slide explain the main differences very good.

enter image description here

*From CSE 403 Lecture 16 , University of Washington (slide created by "Marty Stepp")

柒夜笙歌凉 2024-09-21 01:58:50

使用心理模型确实帮助我理解了这一点,而不是所有没有完全“融入”的解释和文章。

想象一下你的孩子桌子上有一个玻璃盘,他开始玩它。现在,你担心它会破裂。所以,你给他一个塑料盘子。这将是一个模拟(相同的行为,相同的接口,“更软”的实现)。

现在,假设你没有塑料替代品,所以你解释说“如果你继续玩它,它就会破裂!”。这是一个存根,您提前提供了预定义的状态。

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.

情场扛把子 2024-09-21 01:58:50

让我们看看测试替身:

  • Fake:Fake 是具有有效实现的对象,但与生产对象不同。 例如:数据访问对象或存储库的内存中实现。
  • 存根:存根是一个保存预定义数据并在测试期间使用它来应答呼叫的对象。 :需要从数据库抓取一些数据来响应方法调用的对象。

  • 模拟:模拟是注册它们收到的调用的对象。
    在测试断言中,我们可以在 Mock 上验证是否执行了所有预期操作。 :调用电子邮件发送服务的功能。

    有关更多信息,请查看

let see Test Doubles:

  • Fake: Fakes are objects that have working implementations, but not the same as production one. Such as: in-memory implementation of Data Access Object or Repository.
  • 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.

浊酒尽余欢 2024-09-21 01:58:50

我喜欢 Roy Osherove 在他的演讲理解模拟对象中提出的解释:

创建的每个类或对象都是假的。如果你反对它,它就是一个模拟。否则它是一个存根

I like the explanation put out by Roy Osherove in his talk Understanding Mock Objects:

Every class or object created is a fake. It is a mock if you assert against it. Otherwise it is a stub.

那些过往 2024-09-21 01:58:50

我认为他们之间最重要的区别是他们的意图。

让我尝试用为什么存根为什么模拟来解释它

假设我正在为我的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
  • STUB:与 twitter API 的网络连接非常慢,这使我的测试变慢。我知道它会返回时间线,所以我做了一个模拟 HTTP twitter API 的存根,这样我的测试就会运行得非常快,即使我离线也可以运行测试。
  • MOCK:我还没有编写任何 UI 方法,并且我不确定需要为我的 ui 对象编写什么方法。我希望通过编写测试代码了解我的控制器如何与我的 ui 对象协作。

通过编写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

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
  • STUB: The network connection to twitter API is very slow, which make my test slow. I know it will return timelines, so I made a stub simulating HTTP twitter API, so that my test will run it very fast, and I can running the test even I'm offline.
  • MOCK: I haven't written any of my UI methods yet, and I'm not sure what methods I need to write for my ui object. I hope to know how my controller will collaborate with my ui object by writing the test 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

情独悲 2024-09-21 01:58:50

存根

存根是一个对象,用于伪造具有预编程行为的方法。您可能希望使用此方法而不是现有方法,以避免不必要的副作用(例如,存根可能会进行虚假的提取调用,返回预编程的响应,而不实际向服务器发出请求)。

模拟

模拟是一个对象,用于伪造具有预编程行为以及预编程期望的方法。如果这些期望没有得到满足,那么模拟将导致测试失败(例如,模拟可能会进行虚假的获取调用,返回预编程的响应,而不实际向服务器发出预期的请求例如,第一个参数为 "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.

垂暮老矣 2024-09-21 01:58:50

我正在阅读单元测试的艺术,并偶然发现了以下定义:

是一个通用术语,可用于描述存根或模拟对象(手写或其他方式),因为它们看起来都像真实对象。假货是存根还是模拟取决于它在当前测试中的使用方式。如果它用于检查交互(断言反对),那么它是一个模拟对象。否则,它是一个存根

I was reading The Art of Unit Testing, and stumbled upon the following definition:

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.

绳情 2024-09-21 01:58:50
  • 存根与模拟
    • 存根
      1. 为方法调用提供具体答案
        • 例如:myStubbedService.getValues() 仅返回测试代码所需的字符串
      2. 被测试代码用来隔离它
      3. 测试不能失败
        • 例如:myStubbedService.getValues() 仅返回存根值
      4. 经常实现抽象方法
    • 模拟
      1. 存根的“超集”;可以断言某些方法被调用
        • 例如:验证 myMockedService.getValues() 仅被调用一次
      2. 用于测试被测代码的行为
      3. 可能会失败测试
        • 例如:验证 myMockedService.getValues() 是否被调用过一次;验证失败,因为我的测试代码未调用 myMockedService.getValues()
      4. 经常模拟接口
  • Stubs vs. Mocks
    • Stubs
      1. provide specific answers to methods calls
        • ex: myStubbedService.getValues() just return a String needed by the code under test
      2. used by code under test to isolate it
      3. cannot fail test
        • ex: myStubbedService.getValues() just returns the stubbed value
      4. often implement abstract methods
    • Mocks
      1. "superset" of stubs; can assert that certain methods are called
        • ex: verify that myMockedService.getValues() is called only once
      2. used to test behaviour of code under test
      3. can fail test
        • ex: verify that myMockedService.getValues() was called once; verification fails, because myMockedService.getValues() was not called by my tested code
      4. often mocks interfaces
丑疤怪 2024-09-21 01:58:50

模拟:帮助模拟和检查结果交互。这些相互作用
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.

enter image description here

source : Unit Testing Principles, Practices, and Patterns - Manning

Oo萌小芽oO 2024-09-21 01:58:50

他使用的通用术语是测试替身(想想特技替身)。测试替身是一个通用术语,适用于出于测试目的而替换生产对象的任何情况。 Gerard 列出了各种类型的 double:

  • Dummy 对象被传递但从未实际使用。通常它们仅用于填充参数列表。
  • Fake 对象实际上具有有效的实现,但通常会采取一些捷径,这使得它们不适合生产(InMemoryTestDatabase 就是一个很好的例子)。
  • 存根为测试期间拨打的电话提供预设答案,通常根本不响应测试编程之外的任何内容。
  • 间谍是存根,还根据它们的调用方式记录一些信息。其中一种形式可能是记录发送的消息数量的电子邮件服务(也称为部分模拟)。
  • 模拟是根据期望进行预编程的,这些期望形成了它们预期接收的调用的规范。如果他们收到不期望的呼叫,他们可以抛出异常,并在验证过程中进行检查,以确保他们收到了他们期望的所有呼叫。

来源

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:

  • Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
  • Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an InMemoryTestDatabase is a good example).
  • Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.
  • Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent(also called Partial Mock).
  • Mocks are pre-programmed with expectations which form a specification of the calls they are expected to receive. They can throw an exception if they receive a call they don't expect and are checked during verification to ensure they got all the calls they were expecting.

Source

征棹 2024-09-21 01:58:50

存根

存根是一个保存预定义数据并在测试期间使用它来应答调用的对象。当您不能或不想涉及会用真实数据回答或具有不良副作用的对象时,可以使用它。

一个示例可以是需要从数据库获取一些数据以响应方法调用的对象。我们引入了一个存根并定义了应该返回哪些数据,而不是真正的对象。

输入图片描述这里

存根示例:

public class GradesService {

   private final Gradebook gradebook;

   public GradesService(Gradebook gradebook) {
       this.gradebook = gradebook;
   }

   Double averageGrades(Student student) {
       return average(gradebook.gradesFor(student));
   }
}

您无需从成绩册商店调用数据库来获取真实的学生成绩,而是预先配置存根并将返回成绩。您定义了足够的数据来测试平均计算算法。

public class GradesServiceTest {

   private Student student;
   private Gradebook gradebook;

   @Before
   public void setUp() throws Exception {
       gradebook = mock(Gradebook.class);
       student = new Student();
   }

   @Test
   public void calculates_grades_average_for_student() {
       //stubbing gradebook
       when(gradebook.gradesFor(student)).thenReturn(grades(8, 6, 10)); 

       double averageGrades = new GradesService(gradebook).averageGrades(student);

       assertThat(averageGrades).isEqualTo(8.0);
   }
}

Mock

模拟是注册它们收到的调用的对象。在测试断言中,您可以在模拟上验证是​​否执行了所有预期操作。当您不想调用生产代码或者没有简单的方法来验证预期代码是否已执行时,您可以使用模拟。没有返回值,也没有简单的方法来检查系统状态更改。一个示例可以是调用电子邮件发送服务的功能。

您不希望每次运行测试时都发送电子邮件。此外,在测试中验证是否发送了正确的电子邮件并不容易。您唯一能做的就是验证我们测试中所执行的功能的输出。在其他世界中,验证是否调用了电子邮件发送服务。

输入图像描述这里

模拟示例:

public class SecurityCentral {

   private final Window window;
   private final Door door;

   public SecurityCentral(Window window, Door door) {
       this.window = window;
       this.door = door;
   }

   void securityOn() {
       window.close();
       door.close();
   }
}

您不想关闭真实的门来测试安全方法是否有效,对吗?相反,您将门和窗模拟对象放置在测试代码中。

public class SecurityCentralTest {

   Window windowMock = mock(Window.class);
   Door doorMock = mock(Door.class);

   @Test
   public void enabling_security_locks_windows_and_doors() {
       SecurityCentral securityCentral = new SecurityCentral(windowMock, doorMock);

       securityCentral.securityOn();

       verify(doorMock).close();
       verify(windowMock).close();
   }
}

非常感谢 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.

enter image description here

example of Stub:

public class GradesService {

   private final Gradebook gradebook;

   public GradesService(Gradebook gradebook) {
       this.gradebook = gradebook;
   }

   Double averageGrades(Student student) {
       return average(gradebook.gradesFor(student));
   }
}

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.

public class GradesServiceTest {

   private Student student;
   private Gradebook gradebook;

   @Before
   public void setUp() throws Exception {
       gradebook = mock(Gradebook.class);
       student = new Student();
   }

   @Test
   public void calculates_grades_average_for_student() {
       //stubbing gradebook
       when(gradebook.gradesFor(student)).thenReturn(grades(8, 6, 10)); 

       double averageGrades = new GradesService(gradebook).averageGrades(student);

       assertThat(averageGrades).isEqualTo(8.0);
   }
}

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.

enter image description here

Example of Mock:

public class SecurityCentral {

   private final Window window;
   private final Door door;

   public SecurityCentral(Window window, Door door) {
       this.window = window;
       this.door = door;
   }

   void securityOn() {
       window.close();
       door.close();
   }
}

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.

public class SecurityCentralTest {

   Window windowMock = mock(Window.class);
   Door doorMock = mock(Door.class);

   @Test
   public void enabling_security_locks_windows_and_doors() {
       SecurityCentral securityCentral = new SecurityCentral(windowMock, doorMock);

       securityCentral.securityOn();

       verify(doorMock).close();
       verify(windowMock).close();
   }
}

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

月下客 2024-09-21 01:58:50

假货是一个通用术语,可用于描述存根
或模拟对象(手写或其他方式),因为它们看起来都像
真实的物体。

假货是存根还是模拟取决于它的使用方式
当前的测试。如果它用于检查交互(断言反对),那么它是
模拟对象。否则,它就是一个存根。

Fakes 确保测试顺利进行。这意味着未来测试的读者将了解伪造对象的行为,而无需阅读其源代码(无需依赖外部资源)。

试运行顺利是什么意思?
例如在下面的代码中:

 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");
                }
            }
        }

您想要测试 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:

 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");
                }
            }
        }

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.

千鲤 2024-09-21 01:58:50

来自 jMock 开发人员的论文模拟角色,而不是对象

存根是返回固定代码的生产代码的虚拟实现
结果。模拟对象充当存根,但也包括断言
检测目标对象与其邻居的交互。

因此,主要区别在于:

  • 对存根设置的期望通常是通用的,而对模拟设置的期望可能更“聪明”(例如,在第一次调用时返回 this,在第二次调用时返回 this 等)。
  • 存根主要用于设置SUT的间接输入,而模拟可用于测试间接输入和间接 SUT 的输出。

总而言之,同时也试图消除 Fowler 的文章标题中的混乱:模拟是存根,但它们不仅仅是存根

Right from the paper Mock Roles, not Objects, by the developers of jMock :

Stubs are dummy implementations of production code that return canned
results. Mock Objects act as stubs, but also include assertions to
instrument the interactions of the target object with its neighbours.

So, the main differences are:

  • expectations set on stubs are usually generic, while expectations set on mocks can be more "clever" (e.g. return this on the first call, this on the second etc.).
  • stubs are mainly used to setup indirect inputs of the SUT, while mocks can be used to test both indirect inputs and indirect outputs of the SUT.

To sum up, while also trying to disperse the confusion from Fowler's article title: mocks are stubs, but they are not only stubs.

葬シ愛 2024-09-21 01:58:50

我看到了 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.

绝不放开 2024-09-21 01:58:50

那里有很多有效的答案,但我认为值得一提鲍勃叔叔的这个表格:
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!

懒猫 2024-09-21 01:58:50

模拟既是一个技术对象,又是一个功能对象。

模拟是技术性的。由于字节代码生成,它确实是由模拟库创建的(EasyMock、JMockit 和最近的 Mockito 因这些而闻名)。
模拟实现是生成的,我们可以检测它在调用方法时返回特定值,但也可以进行其他一些操作,例如验证模拟方法是否是使用某些特定参数(严格检查)或任何参数(无严格检查)调用。

实例化模拟:

@Mock Foo fooMock

记录行为:

when(fooMock.hello()).thenReturn("hello you!");

验证调用:

verify(fooMock).hello()

这些显然不是实例化/覆盖 Foo 类/行为的自然方式。这就是为什么我提到技术方面。

但是模拟也具有功能性,因为它是我们需要与 SUT 隔离的类的实例。通过记录其行为,我们可以在 SUT 中使用它,就像使用存根一样。


存根只是一个功能对象:它是我们需要与 SUT 隔离的类的实例,仅此而已。
这意味着我们的单元测试期间所需的存根类和所有行为固定装置都必须明确定义。
例如,要存根 hello() 需要子类化 Foo 类(或实现其接口)并重写 hello()

public class HelloStub extends Hello{    
  public String hello { 
      return "hello you!"; 
  }
}

如果另一个测试场景需要另一个值返回,我们可能需要定义一个通用方法来设置返回:

public class HelloStub extends Hello{    
  public HelloStub(String helloReturn){
       this.helloReturn = helloReturn;
  }
  public String hello { 
      return helloReturn; 
  }
}

其他场景:如果我有一个副作用方法(无返回)并且我会检查该方法是否被调用,我应该可能在存根类中添加了一个布尔值或计数器来计算该方法被调用的次数。


结论

存根通常需要大量开销/代码来为单元测试编写。由于提供了开箱即用的记录/验证功能,模拟可以防止什么。
这就是为什么现在随着优秀的模拟库的出现,存根方法在实践中很少使用。


关于 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 :

@Mock Foo fooMock

Recording a behavior :

when(fooMock.hello()).thenReturn("hello you!");

Verifying an invocation :

verify(fooMock).hello()

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 the Foo class (or implements its interface it has it) and to override hello() :

public class HelloStub extends Hello{    
  public String hello { 
      return "hello you!"; 
  }
}

If another test scenario requires another value return, we would probably need to define a generic way to set the return :

public class HelloStub extends Hello{    
  public HelloStub(String helloReturn){
       this.helloReturn = helloReturn;
  }
  public String hello { 
      return helloReturn; 
  }
}

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.

原野 2024-09-21 01:58:50

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:

  • Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.
  • Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.

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)

一人独醉 2024-09-21 01:58:50

加上有用的答案,使用 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.

寂寞陪衬 2024-09-21 01:58:50

存根帮助我们运行测试。如何?它提供有助于运行测试的值。这些值本身并不真实,我们创建这些值只是为了运行测试。例如,我们创建一个 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.

心凉 2024-09-21 01:58:50

请参阅下面使用 C# 和 Moq 框架的模拟与存根的示例。 Moq 没有 Stub 的特殊关键字,但您也可以使用 Mock 对象来创建存根。

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";
        }
    }
}

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.

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";
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文