如何进行集成测试的模拟?

5
阅读《Growing Object Orientated Software Guided by Tests》后,我了解了测试隔离和测试脆弱性。每个测试都应该非常具体地针对一段代码或功能,测试覆盖的代码重叠部分应该尽量减少。暗示理想情况是代码中的每个更改都应只导致一个测试失败。避免花费时间去检查多个失败的测试以确认一个更改是否是原因,并且测试修改是否修复它。
现在,对于单元测试来说,这似乎很容易,它们的本质非常隔离。但是,在集成测试中,似乎很难避免多个测试使用相同的代码路径,特别是当与单元测试一起运行时。
因此,我的问题是,在进行集成测试时应模拟哪些依赖项?是否需要模拟任何内容?是否应该测试单个执行路径,并模拟所有与此代码路径无直接关联的副作用?
我正在玩弄使用成对集成测试的想法。测试两个对象之间的一个关系,并模拟其他所有内容。然后,这些对象中的任何一个发生更改时,对其他集成测试的影响应最小化,并通过成对形成完整的端到端测试链。
谢谢任何信息。 编辑:只是为了澄清,我基本上是在问“如何避免在开发过程中出现大量失败的集成测试?”。我认为这可以通过使用模拟来实现,这就是我询问应该模拟什么的原因。

更新:我找到了一场非常有趣的由J.B.Rainsberger主讲的关于集成测试的演讲,我认为这个演讲对此有很好的回答,尽管也有点争议性。它的标题是“集成测试是一个骗局”,所以你可以猜到,他根本不提倡集成测试(端到端类型的测试)。他的观点是集成测试总是远远低于彻底测试可能的交互作用所需的数量(由于组合爆炸),并且可能会给人一种虚假的信心。 相反,他推荐了他称之为协作测试和契约测试。这是一个90分钟的演讲,不幸的是白板不是很清晰,也没有代码示例,所以我还在理解中。当我有一个清晰的解释时,我会在这里写下来!除非有人比我先写了..

这里是契约测试的简要概述。听起来像是设计契约类型的断言,我认为这可以在C++中通过实现非虚拟接口模式来实现。

http://thecodewhisperer.tumblr.com/post/1325859246/in-brief-contract-tests

《集成测试是骗局》视频演讲: http://www.infoq.com/presentations/integration-tests-scam

摘要:

集成测试是一种骗局。你可能只编写了需要全面测试的2-5%的集成测试。你可能在各个地方重复编写单元测试。你的集成测试可能在各个地方重复彼此。当集成测试失败时,谁知道哪里出了问题?学习解决问题的双管齐下方法:协作测试和契约测试。


1
虽然我还没有读过《Growing Object-Oriented Software, Guided by Tests》(我真惭愧),但我非常确定它在说每次更改只应该有一个测试失败时,并不是在谈论集成测试。 - Daniel Hilgarth
我明白了,你认为测试脆弱性更适用于单元测试而不是集成测试?我猜集成测试被认为本质上更脆弱,更容易失败? - Coran
1
不,恰恰相反。集成测试测试的是一个更大的图景,只要它们所测试的功能没有改变,它们就不应该失败。 - Daniel Hilgarth
问题在于当行为发生有效变化时。如果所有集成测试都使用某个特定的对象进行更改,那么所有这些测试都将失败并需要关注。而且这可能是大量的测试,这就是我试图避免的。 - Coran
如果您正在更改功能,则必须更改一些测试。这是无法避免的。但实际上,我敢打赌,为响应有效的行为更改所需更改的测试数量比您想象的要小。 - Daniel Hilgarth
4个回答

3

在集成测试中,您应该模拟最少的依赖关系以使测试正常工作,但不能过少 :-)

由于您显然要在集成测试期间测试系统中组件的集成,因此尽可能使用实际实现。但是,有一些组件显然需要模拟,比如您不想让集成测试向用户发送电子邮件。如果您不模拟这些依赖项,您显然会模拟太少。

顺便说一句,这并不意味着您不允许集成测试发送电子邮件,但至少您希望用一个只向某个内部测试邮箱发送邮件的邮件组件替换原来的邮件组件。


我遇到的问题是,理论上我可以模拟一切,测试也可以正常工作。例如,对于几个对象协作计算值的测试,我可以只使用模拟返回该值。 也许可以采用某种TDD风格的方法,一开始就模拟所有内容,使其正常工作,然后逐渐扩展集成测试断言,直到模拟不足并需要真正的代码,总代码覆盖率是最终目标?我不知道...有时想起来就头痛。 - Coran
1
“总代码覆盖率是最终目标”?不,已交付的、可工作的软件才是最终目标。适当高的代码覆盖率可以帮助您实现“可工作”。追求100%并不能帮助交付,反而可能会损害“可工作”的目标。 - Michael Lloyd Lee mlk
如果有这样一个目标,那就是完全覆盖,它不是指完全的代码覆盖率,而是指完全的功能覆盖率,也就是每个功能都被你的测试所覆盖。 - Daniel Hilgarth

2
对于集成测试,我倾向于模拟服务而不是表示,例如使用Mirage而不是第三方REST API,以及使用Dumpster而不是真实的SMTP服务器。
这意味着您可以测试代码的所有层,但不会测试第三方服务,因此您可以自由地重构而不必担心测试失败。

1

单元测试应该有模拟对象,但集成测试应该尽量少使用模拟对象(否则集成的是什么?)我认为进行成对模拟是过度的;这将导致大量测试爆炸,每个测试可能需要很长时间和大量复制粘贴代码,如果需求变化或稍后添加新功能,则更改将很麻烦。

我认为在集成测试中不使用任何模拟是可以的。您应该在单元测试中模拟所有内容,以了解每个单独的单元是否按照预期在隔离状态下工作。集成测试测试所有东西是否正确地连接在一起。


我试图解决的主要问题是,当存在有效更改时,如何避免大量失败的集成测试?如果没有模拟,所有与更改相关的内容都将失败并需要关注。 - Coran
根据所发生的变化,我认为集成测试失败是一件好事。如果一个依赖项的接口发生了变化,集成测试应该会失败,因为我们正在测试我们是否正确地与我们的依赖项进行通信。 - dkatzel
但是我想问你的问题是,有多少个测试失败才算可以接受?理论上,如果有数百个测试,它们都依赖于某些基本组件,比如数据库,那么如果该数据库发生更改,可能会导致数百个测试失败。 - Coran
取决于数据库的更改方式以及您是否很好地分离了数据库层。例如,假设您继承了一个糟糕的数据库设计,但对象模型是合理的。您可以重构一些数据库设计。在我看来,不应该出现任何集成测试失败的情况。 - Michael Lloyd Lee mlk

0

讨论JB Rainsberger在“Integration Tests are a Scam”中描述的合同/协作者测试模式(如上所述的问题)。关于这里的问题-我理解他的讲话是指当您拥有服务端和客户端代码时,就不需要任何集成测试。相反,您应该能够依赖实现合同的模拟。

这个讲话是高层次描述该模式的好参考,但对于我来说并没有详细说明如何定义或从协作者引用合同。

一个常见的需要合同/协作者模式的例子是API的服务器/客户端之间(您拥有两者的代码)。以下是我的实现方式:

定义合同:

首先定义API Schema,如果您的API使用JSON,则可以考虑JSONSchema。模式定义可以被视为API的“合同”。(顺便说一句,如果您要这样做,请确保您了解RAML或Swagger,因为它们可以使编写JSONSchema API变得更加容易)

创建实现合同的固定装置:

  • 在服务器端,模拟客户端请求以允许对请求/响应进行单元测试。为此,您将创建客户端请求固定装置(也称为模拟)。一旦您定义了API,请使用JSONSchema验证固定装置以确保其符合规范。有许多模式验证器-我目前使用AJV(Javascript)和jsonschema(Python),但大多数语言都应该有一个实现。

  • 在客户端方面,您可能会模拟服务器响应以允许对请求进行单元测试。按照服务器相同的模式,通过JSONSchema验证请求和响应固定装置。

如果客户端和服务器都根据合同验证其装置,那么每当API合同发生变化时,双方过时的实现将无法通过JSONSchema验证,您将知道是时候更新您的装置以及可能依赖于这些装置的代码了。

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