如果我已经有集成测试,是否需要单元测试?


47

如果我已经对我的程序进行了集成测试,并且都通过了测试,那么我有很好的感觉,它可以工作。那么编写/添加单元测试的原因是什么?由于无论如何我都必须编写集成测试,所以我只想为集成测试未涵盖的部分编写单元测试。

我知道单元测试优于集成测试的好处是

  • 体积小,因此运行速度快(但是,通过集成测试已经测试了添加新单元以测试某件东西,这意味着我的总体测试服变得越来越大,并且运行时间更长)
  • 由于只测试一件事,因此更容易找到错误(但是,当我的集成测试失败时,我可以开始编写单元测试来验证每个单独的部分)
  • 查找集成测试中可能未捕获的错误。例如掩盖/抵消错误。(但是,如果我的集成测试通过了所有测试,这意味着即使存在一些隐藏的错误,我的程序也可以运行。因此,找到/修复这些错误并不是真正的高优先级,除非它们开始破坏未来的集成测试或引起性能问题)

而且,我们总是希望编写更少的代码,但是编写单元测试需要更多的代码(主要是安装模拟对象)。我的一些单元测试和集成测试之间的区别在于,在单元测试中,我使用模拟对象,而在集成测试中,我使用真实对象。其中有很多重复项,即使在测试中,我也不喜欢重复的代码,因为这会增加更改代码行为的开销(重构工具无法始终保持所有工作)。


出于好奇,您的集成测试覆盖了多少?另外,如果您有一些复杂的逻辑,您的集成测试是覆盖全部还是部分,而哪些对业务或随机性很重要?
civan

这个问题似乎模棱两可。假设我有一个调用交互器的Rails控制器方法(github.com/collectiveidea/interactor)。我已经决定为我的控制器方法编写集成测试(因为没有它们,我无法相信我的API端点可以工作)。这里至少有两个问题:(1)我是否还应该为控制器方法编写单元测试(即,我是否应该编写测试与集成测试行为相同的单元测试),以及(2)我还应该编写单元测试吗?为我的交互者?在某些业务环境中,我会以不同的方式回答这两个问题。
丹尼尔

Answers:


33

您已经提出了支持和反对单元测试的良好论据。因此,您必须问自己:我是否会在积极的论点中看到价值大于在消极的论点中付出的代价? 我当然会这样做:

  • 小型快速是单元测试的一个不错的方面,尽管绝不是最重要的。
  • 轻松查找错误非常有价值。许多有关专业软件开发的研究表明,错误的成本随着其年龄的增长和在软件交付渠道中的下降而急剧上升。
  • 寻找被掩盖的错误很有价值。当您知道某个特定组件的所有行为均已验证时,可以放心使用以前从未使用过的方式使用它。如果唯一的验证是通过集成测试,则您仅知道其当前使用行为正确。
  • 在现实情况下,模拟是昂贵的,而维护模拟则是双重的。实际上,当模拟“有趣的”对象或接口时,您甚至可能需要测试来验证您的模拟对象正确地对真实对象进行建模!

在我的书中,利弊大于利弊。


2
在现实世界中,嘲笑不是更便宜吗?您只需要设置对模拟接收的消息的期望,并指定模拟返回的消息。
Dogweather

10
@Dogweather Mocks首次创建时效果很好。随着时间的流逝和真实对象的改变,模拟必须随之改变。10年和6,000个单元测试即将到来,很难知道您的“成功”测试是否真正成功。
罗斯·帕特森

1
顺便说一句,“ 10年和6,000个单元测试”是来自个人经验的数字,而不是夸大其词。
罗斯·帕特森

3
我早在2004或2005年就开始编写自动化的开发人员测试(单元和集成,但主要是后者),并为Java开发了高级模拟库。很多次,我看到多个集成测试由于相同的原因而中断(例如数据库更改或网络更改),有时我为可重用组件编写“较低级别”的集成测试,但我仍然更喜欢包含集成测试。在大多数情况下,带有模拟的隔离单元测试根本不值得。我仅将模拟应用于集成测试。
罗杰里奥2015年

您的最后一个要点似乎与您所提出的观点相矛盾。在本要点中,您要反对编写不必要的模拟,这是反对为集成测试已涵盖的部分编写单元测试的论据。但是,您争论的重点是为集成测试已经涵盖的部分编写单元测试。您能在这里@RossPatterson阐明您的意图吗?
丹尼尔

8

我认为重新实现现有的集成测试用例作为单元测试没有太大的价值。

对于非tdd遗留应用程序,集成测试通常更容易编写,因为通常要测试的功能紧密耦合,因此孤立的测试单元(=单元测试)可能难以实现/昂贵/不可能。

> Then what are the reasons to write/add unit tests?

我认为,如果您在实际代码之前编写单元测试那么测试驱动的开发最有效。这样,满足测试要求的代码就被清晰地分隔开了,并带有最少的易于测试的外部引用。

如果代码已经存在而没有单元测试,那么以后编写单元测试通常会花费很多额外的工作,因为代码不是为便于测试而编写的。

如果您执行TDD,则自动测试代码很容易。


2
如果我有一个no的遗留应用程序unit-tests,并且想包含unit-tests某些部分,那么您认为最好integration tests为遗留代码写出它更好吗?写完这些之后,您就可以解除紧密耦合,并创建具有可以测试的接口的功能了吗?既然您integration-test已经编写了代码,则可以在重构的每个步骤中验证新版本的代码是否仍可以按需运行?
alpha_989 '18

2
@ alpha_989,这是非常恕我直言,我乐于听取其他想法,但是对于传统代码,我更喜欢集成测试而不是单元测试。在这两种情况下,您都必须确定方法在添加任何类型的测试之前都能正常工作,但是Integration Testing确保与单独的代码元素相反,方法的总体期望能够正常运行。
Britt Wescott

5

集成测试应仅验证几个组件是否按预期工作。各个组件的逻辑是否准确,应通过单元测试进行验证。
大多数方法都有几种可能的执行路径。考虑一下if-then-else的输入变量,这些变量具有意外的值或只是错误的值,等等。通常,开发人员倾向于只考虑快乐的路径:不会出错的正常路径。但就其他路径而言,您有两个选择:您可以让最终用户通过他们在UI中执行的操作来探索这些路径,并希望他们不会使您的应用程序崩溃,或者可以编写断言的单元测试。其他路径的行为,并在必要时采取措施。


4

您在问题中指出的某些原因确实很重要,就其本身而言,很可能会支持单元测试,但YMMV却很有用。例如,您多久运行一次集成测试套件?我在集成测试中的经验是,它们迟早会变得如此缓慢,以至于您每次进行更改时都不会运行它们,并且插入和检测错误之间的时间会增加。

另外,您可能会犯的一个大错误就是相信

Find bug that may not be caught in integration test. e.g. masking/offsetting bugs.

并不重要。您是否希望您的用户为您找到错误?在我看来,信任从集成测试中获得的承保范围很危险,您可以很容易地获得很高的承保范围,但实际上您的测试很少。

我认为针对集成测试的规范参考是JBrains的帖子:

http://www.jbrains.ca/permalink/integrated-tests-are-a-scam-part-1

如果您还没有阅读它们的话。

最终,IMO从设计的单元测试中获得的反馈是非常宝贵的。通过直觉判断设计并依靠集成测试也可能是一个错误。


我真的没有那篇文章。因此,有很多代码路径,并且集成测试无法涵盖所有​​内容……是吗?除非您的单元过于琐碎以至于毫无意义,否则单元测试同样适用。
凯西2015年

这更像是一种反对完全覆盖代码覆盖范围的论点,而不是应该编写大量的单元测试并避免集成测试。
凯西2015年

1

如果您希望修改或重构代码,则必须进行单元测试。单元测试不仅用于演示编写代码时的错误,而且还用于显示在编写更多代码时何时出现新的错误。

是的,最好先编写集成和单元测试,但是即使稍后再编写它们,也具有很多价值。


3
我觉得你没有理由。集成测试还将揭示代码中的错误,并且实际上能够捕获单元测试不会的错误。
凯西2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.