我读过各种关于测试中模仿和存根的文章,包括Martin Fowler的《Mocks Aren't Stubs》,但我仍然不理解其中的区别。
当前回答
存根是向SUT返回值的测试double。
模拟是测试用于验证SUT正确调用依赖项的双重测试。
而且,mock通常是存根
其他回答
看了上面所有的解释,让我试着总结一下:
Stub:让测试运行的一段虚拟代码,但您并不关心它会发生什么。替代实际工作代码。 Mock:在测试中验证是否正确调用的一段虚拟代码。替代实际工作代码。 间谍:一段虚拟代码,用于拦截和验证对实际工作代码的某些调用,从而避免替换所有实际代码。
mock既是技术对象,也是功能对象。
这个模拟是技术性的。它确实是由一个模拟库(EasyMock、JMockit和最近的Mockito都以这些库而闻名)创建的,这要归功于字节代码生成。 生成模拟实现的方式是,我们可以在调用方法时将其设置为返回特定值,但也可以执行其他一些事情,例如验证模拟方法是否使用某些特定参数(严格检查)或任何参数(不严格检查)调用。
实例化一个mock:
@Mock Foo fooMock
记录行为:
when(fooMock.hello()).thenReturn("hello you!");
验证调用:
verify(fooMock).hello()
这些显然不是实例化/重写Foo类/行为的自然方式。这就是为什么我提到技术方面。
但是mock也是功能性的,因为它是我们需要从SUT中隔离出来的类的实例。有了记录在上面的行为,我们就可以在SUT中使用它就像使用存根一样。
存根只是一个函数对象:它是我们需要从SUT中隔离出来的类的实例,仅此而已。 这意味着在单元测试期间需要的存根类和所有行为fixture都必须显式地定义。 例如,存根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;
}
}
其他场景:如果我有一个副作用方法(没有返回),并且我要检查该方法是否被调用,我可能应该在存根类中添加一个布尔值或计数器来计算该方法被调用的次数。
结论
存根通常需要为单元测试编写大量开销/代码。mock因为提供了开箱即用的记录/验证功能而阻止了什么。 这就是为什么现在随着优秀的模拟库的出现,存根方法很少在实践中使用。
关于Martin Fowler文章:当我使用mock和避免存根时,我不认为我是一个“嘲笑主义者”程序员。 但是当真正需要时我使用mock(讨厌的依赖关系),当我测试带有依赖关系的类时,我喜欢测试切片和迷你集成测试,而mock将是一种开销。
存根用于您在测试中设置的具有预期返回值的方法。 mock用于void方法,这些方法在调用时的Assert中进行验证。
Stub
存根是保存预定义数据并在测试期间使用它应答调用的对象。当您不能或不想涉及与真实数据对应或具有不良副作用的对象时,可以使用它。
一个例子可以是需要从数据库获取一些数据以响应方法调用的对象。我们引入了存根而不是实际对象,并定义了应该返回什么数据。
Stub的例子:
public class GradesService {
private final Gradebook gradebook;
public GradesService(Gradebook gradebook) {
this.gradebook = gradebook;
}
Double averageGrades(Student student) {
return average(gradebook.gradesFor(student));
}
}
不是从Gradebook store调用数据库来获取真实的学生成绩,而是预先配置将返回的成绩存根。定义足够的数据来测试平均计算算法。
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
mock是注册它们接收到的调用的对象。在测试断言中,您可以在mock上验证是否执行了所有预期的操作。当您不想调用产品代码或没有简单的方法来验证预期的代码是否被执行时,您可以使用模拟。没有返回值,也没有检查系统状态更改的简单方法。调用电子邮件发送服务的功能就是一个例子。
您不希望每次运行测试时都发送电子邮件。此外,在测试中验证发送的电子邮件是否正确并不容易。您唯一能做的就是验证在我们的测试中执行的功能的输出。在其他情况下,验证是否调用了电子邮件发送服务。
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();
}
}
非常感谢michaowallipski的好文章。欲进一步阅读:
测试双-马丁福勒https://martinfowler.com/bliki/TestDouble.html 测试Double - xUnit模式http://xunitpatterns.com/Test%20Double.html 嘲笑不是存根-马丁福勒https://martinfowler.com/articles/mocksArentStubs.html 命令查询分离- Martin Fowler https://martinfowler.com/bliki/CommandQuerySeparation.html
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("admin@hotmail.com", "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.
推荐文章
- ReferenceError: description没有定义NodeJs
- Mock vs MagicMock
- 我如何使用Jest模拟JavaScript的“窗口”对象?
- 如何检查动态附加的事件监听器是否存在?
- 强制重新测试或禁用测试缓存
- 比较Java中2个XML文档的最佳方法
- 如何模拟低带宽、高延迟的环境?
- 使用Moq验证方法调用
- 我如何“休眠”Dart程序
- 使用Mockito的泛型“any()”方法
- Mockito中检测到未完成的存根
- 尝试模拟datetime.date.today(),但不工作
- 如何用python timeit对代码段进行性能测试?
- 确定bash中是否存在一个函数
- 如何使用“测试”包打印Go测试?