理解存根、伪造对象和模拟对象。

10

我刚开始阅读的是 专业的C#测试驱动开发:使用TDD开发实际应用程序

我很难理解存根(stubs),伪造对象(fakes)和模拟对象(mocks)。据我目前所了解的,它们都是用于单元测试项目的假对象,而模拟对象则是带有条件逻辑的存根。

另一件我认为我已经明白的事情是,模拟对象与依赖注入有关,这个概念昨天我才理解。

我不明白的是我为什么要使用它们。我似乎找不到任何在线上可以正确解释它们的具体例子。

请问有人可以向我解释这些概念吗?


2
阅读此链接 http://xunitpatterns.com/Mocks,%20Fakes,%20Stubs%20and%20Dummies.html 和此链接 http://martinfowler.com/articles/mocksArentStubs.html。 - blank
1
顺便提一下,您也可以阅读这篇文章:http://www.mockobjects.com/files/mockrolesnotobjects.pdf - blank
希望您不介意商业宣传,我们写了一本名为《Growing Object Oriented Software》的书,其中讨论了使用模拟对象的动机。 - Steve Freeman
3个回答

23

根据我以前的阅读,以下是我认为每个术语的含义:

Stub

在这里,您将一个方法的结果存根(stub)为已知值,只是为了让代码运行而没有问题。例如,假设您有以下内容:

public int CalculateDiskSize(string networkShareName)
{
    // This method does things on a network drive.
}

你并不关心这个方法的返回值,因为它并不重要。此外,如果网络驱动器不可用,执行时可能会导致异常。因此,你需要对结果进行桩处理,以避免出现潜在的执行问题。

所以最终你会做出类似以下的操作:

sut.WhenCalled(() => sut.CalculateDiskSize()).Returns(10);

伪造

使用伪造数据,或创建对象的伪造实例。典型的例子是仓储类。看看这个方法:

public int CalculateTotalSalary(IList<Employee> employees) { }

通常上述方法将传递从数据库中读取的员工集合。但是在单元测试中,您不希望访问数据库。因此,您需要创建一个虚假的员工列表:

IList<Employee> fakeEmployees = new List<Employee>();

你可以向fakeEmployees添加项目并断言预期结果,例如总工资。

模拟对象

在使用模拟对象时,您打算验证这些模拟对象上的某些行为或数据。例如:

您希望验证在测试运行期间执行了特定方法,以下是使用Moq模拟框架的通用示例:

public void Test()
{
    // Arrange.
    var mock = new Mock<ISomething>();
    
    mock.Expect(m => m.MethodToCheckIfCalled()).Verifiable();

    var sut = new ThingToTest();
    
    // Act.
    sut.DoSomething(mock.Object);

    // Assert
    mock.Verify(m => m.MethodToCheckIfCalled());
}
希望以上内容能稍微澄清一些问题。 编辑: Roy Osherove是测试驱动开发的著名倡导者,他在这个主题上有一些很好的信息。你可能会发现它非常有用:http://artofunittesting.com/

非常有信息量。我一直以为伪造对象和模拟对象是可以互换的。在这里我每天都学到新东西:) +1 - bas
'Mocks' 中的 'm' 是什么?ISomething 是我们正在测试的类吗? - fersarr
m是传递到Expect()函数中的参数。它是一个lambda表达式。它没有在任何地方声明,只是存在于Expect() lambda内部。var mock = new Mock<ISomething>();意味着您正在创建一个实现ISomething的对象,因此ISomething是一个接口,而不是一个对象。然后,您可以使用mock来控制方法在调用时如何响应,例如通过Expect()函数。 - Jason Evans

3
它们都是测试替身的变体。这里有一个很好的参考资料,解释了它们之间的区别:http://xunitpatterns.com/Test%20Double.html 此外,从马丁·福勒(Martin Fowler)的文章中可以了解到:http://martinfowler.com/articles/mocksArentStubs.html Meszaros将“测试替身”作为通用术语,用于表示任何一种在测试目的下代替真实对象的虚拟对象。该名称来源于电影中的替身演员概念。(他的一个目标是避免使用已经广泛使用的任何名称。)然后,Meszaros定义了四种特定的替身:
1. 虚拟对象:被传递但实际上从未被使用。通常只用于填充参数列表。 2. 伪造对象:实际上具有工作实现,但通常采取某些捷径,使其不适合生产环境(内存数据库是一个很好的例子)。 3. 存根提供预先设置的答案以响应测试期间进行的调用,通常对程序之外的所有内容都没有响应。存根还可以记录关于调用的信息,例如记住发送的消息的电子邮件网关存根,或者仅记住发送的消息数量。 4. 模拟对象就是我们在这里讨论的:预先编程的对象,其中包含对它们应该接收到的调用的规范。
在这些替身中,只有模拟对象坚持行为验证。其他替身可以使用状态验证。在实施阶段,模拟对象实际上像其他替身一样运作,因为它们需要使SUT相信它正在与其真正的协作者交谈。

0

这本 PHP Unit 的手册对我作为入门者帮助很大:

“有时候,由于系统受到其他无法在测试环境中使用的组件的影响,测试系统变得非常困难。这可能是因为它们不可用,它们不能返回所需的测试结果,或者执行它们会产生不良的副作用。在其他情况下,我们的测试策略要求我们更多地控制或查看 SUT 的内部行为。” 更多信息:https://phpunit.de/manual/current/en/test-doubles.html

当我寻找关于“测试替身”(如模拟、伪造、存根等)的更好的“介绍”时,我发现了更多内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接