我知道所谓的单元测试和集成测试的教科书定义。我好奇的是什么时候该写单元测试了……我将写它们来覆盖尽可能多的类集。

例如,如果我有一个Word类,我将为Word类编写一些单元测试。然后,我开始编写我的Sentence类,当它需要与Word类交互时,我经常会编写单元测试,这样它们就可以同时测试Sentence和Word……至少在他们相互作用的地方。

这些测试本质上变成集成测试了吗?因为它们现在测试这两个类的集成,还是仅仅是一个跨越两个类的单元测试?

一般来说,由于这条不确定的界线,我很少实际编写集成测试……或者我是否使用成品来查看所有部件是否正常工作,即实际的集成测试,即使它们是手动的,并且很少在每个单独的功能范围之外重复?

我是否误解了集成测试,或者集成测试和单元测试之间真的只有很小的区别?


当前回答

如果class1的单元测试是测试class1的特性,而class2的单元测试是测试它的特性,并且它们不访问数据库,我想我仍然会把两个相互作用的类称为单元测试。

当一个测试运行了我的堆栈的大部分,甚至到达数据库时,我把它称为集成测试。

我真的很喜欢这个问题,因为TDD讨论有时对我来说有点太纯粹了,看到一些具体的例子对我来说是件好事。

其他回答

你测试的性质

模块X的单元测试是仅在模块X中预期(并检查)问题的测试。

许多模块的集成测试是一种预期由模块之间的合作产生的问题的测试,这样单独使用单元测试就很难发现这些问题。

从以下方面考虑测试的性质:

Risk reduction: That's what tests are for. Only a combination of unit tests and integration tests can give you full risk reduction, because on the one hand unit tests can inherently not test the proper interaction between modules and on the other hand integration tests can exercise the functionality of a non-trivial module only to a small degree. Test writing effort: Integration tests can save effort because you may then not need to write stubs/fakes/mocks. But unit tests can save effort, too, when implementing (and maintaining!) those stubs/fakes/mocks happens to be easier than configuring the test setup without them. Test execution delay: Integration tests involving heavyweight operations (such as access to external systems like DBs or remote servers) tend to be slow(er). This means unit tests can be executed far more frequently, which reduces debugging effort if anything fails, because you have a better idea what you have changed in the meantime. This becomes particularly important if you use test-driven development (TDD). Debugging effort: If an integration test fails, but none of the unit tests does, this can be very inconvenient, because there is so much code involved that may contain the problem. This is not a big problem if you have previously changed only a few lines -- but as integration tests run slowly, you perhaps did not run them in such short intervals...

记住,集成测试仍然可能存根/伪造/模拟它的一些依赖项。 这在单元测试和系统测试(最全面的集成测试,测试所有系统)之间提供了大量的中间地带。

务实的方法使用两者

因此,一种务实的方法是:尽可能灵活地依赖集成测试,在风险太大或不方便的地方使用单元测试。 这种思维方式可能比单元测试和集成测试的一些教条主义区分更有用。

我在采访中经常被问到这个问题。到目前为止,我一直在吹嘘我的专业知识,并对组件测试和验收测试侃侃而谈。

多年来,我只了解集成和单元测试。作为一个单独的开发人员,我可以编写单元测试来磨练我的技能,但我并不总是这么做。

单元测试

这是一个关键的区别。单元测试易于实现和执行,理想情况下不需要依赖。这就是嘲笑的意义所在。不模仿所有内容通常更容易,特别是在覆盖您所编写的其他函数的情况下。也许更简单,但这不是单元测试的思想。

我要重申,单元测试意味着易于运行且规模小。它们的失败可以让我们立即了解bug是在哪里引入的。

下面是测试的等级,从底部的廉价和丰富到顶部的缓慢、昂贵和很少:

还有几个层可以概念化,但为了清晰起见,省略了这些层。

集成测试

通过集成测试,您可以考虑引入严重的外部依赖项,例如虚拟机、虚拟网络和设备。可能您可以使用实际的调制解调器、路由器和防火墙,只要这些花费是合理的。

这些不会在本地运行,而是在构建服务器上运行。本地Jenkins和基于云的CI提供商的组合满足了这一需求。

其他测试术语

这是我在工业界工作了几年的体会。我们可以讨论组件测试,并得到一个定义,但如果这个定义没有被广泛使用,那么它就失去了价值。

验收测试就是我们所说的业务单元或客户需求。这些将引导一切的方向,并位于金字塔的顶端(想象一个美元符号)。

端到端测试是集成测试的同义词,但我在网上注意到它被放在上面。我想它可能与验收测试和集成测试更相关,后者往往更详细,涉众的兴趣更少(尽管部门内部有巨大的兴趣)。

在我看来,答案是“为什么这很重要?”

是不是因为您只做单元测试而不做集成测试?反之亦然?当然不是,你应该尝试两者兼得。

是因为单元测试需要快速、隔离、可重复、自我验证和及时,而集成测试不需要吗?当然不是,所有的测试都应该是这样。

这是因为您在单元测试中使用模拟,而在集成测试中不使用它们吗?当然不是。这意味着,如果我有一个有用的集成测试,我不允许为某些部分添加模拟,担心我将不得不将我的测试重命名为“单元测试”或将其交给另一个程序员来处理。

是不是因为单元测试测试一个单元,而集成测试测试许多单元?当然不是。这有什么实际意义呢?关于测试范围的理论讨论在实践中无论如何都是行不通的,因为术语“单元”完全依赖于上下文。在类级别上,单元可能是一个方法。在程序集级别上,单元可能是类,而在服务级别上,单元可能是组件。 甚至类也会使用其他类,那么哪个是单位呢?

这无关紧要。

测试很重要,F.I.R.S.T也很重要,对定义吹毛求疵是浪费时间,只会让测试新手感到困惑。

我的10位:D

我总是被告知单元测试是对单个组件的测试——应该充分地进行测试。现在,这往往有很多层次,因为大多数组件是由较小的部件组成的。对我来说,单元是系统的功能部分。所以它必须提供一些有价值的东西(例如,不是字符串解析的方法,而是HtmlSanitizer)。

集成测试是下一步,它采用一个或多个组件,并确保它们按照应有的方式一起工作。然后,你是在担心组件如何单独工作的复杂,但当你输入html到你的htmledcontrol,它以某种神奇的方式知道它是否有效。

这是一条真正可移动的线。我宁愿更专注于让该死的代码完全停止工作^_^

对我来说,关键的区别在于,集成测试揭示了一个特性是有效的还是坏的,因为它们在接近现实的场景中强调了代码。它们调用一个或多个软件方法或功能,并测试它们是否按预期工作。

相反,测试单个方法的单元测试依赖于(通常是错误的)软件其余部分正常工作的假设,因为它显式地模拟每个依赖项。

因此,当实现某个特性的方法的单元测试显示为绿色时,并不意味着该特性正在工作。

假设你有一个这样的方法:

public SomeResults DoSomething(someInput) {
  var someResult = [Do your job with someInput];
  Log.TrackTheFactYouDidYourJob();
  return someResults;
}

做某事对你的客户来说非常重要:它是一个特性,是唯一重要的事情。这就是为什么您通常要编写一个Cucumber规范来断言它:您希望验证和交流该特性是否有效。

Feature: To be able to do something
  In order to do something
  As someone
  I want the system to do this thing

Scenario: A sample one
  Given this situation
  When I do something
  Then what I get is what I was expecting for

毫无疑问:如果测试通过了,你就可以断言你交付了一个可以工作的特性。这就是所谓的业务价值。

如果你想为DoSomething写一个单元测试,你应该假装(使用一些模拟)其余的类和方法都在工作(也就是说:方法所使用的所有依赖项都在正确工作),并断言你的方法是工作的。

在实践中,你可以这样做:

public SomeResults DoSomething(someInput) {
  var someResult = [Do your job with someInput];
  FakeAlwaysWorkingLog.TrackTheFactYouDidYourJob(); // Using a mock Log
  return someResults;
}

你可以使用依赖注入,或者一些工厂方法,或者任何模拟框架,或者只是扩展测试中的类。

假设Log.DoSomething()中有一个错误。 幸运的是,Gherkin规范会找到它,您的端到端测试将失败。

这个特性不会工作,因为日志坏了,而不是因为[Do your job with someInput]没有做它的工作。顺便说一下,[Do your job with someInput]是该方法的唯一责任。

同样,假设Log在100个其他特性中使用,在100个其他类的100个其他方法中使用。

是的,100个功能都会失败。但幸运的是,100个端到端测试也失败了,并揭示了这个问题。是的,他们说的是实话。

这是非常有用的信息:我知道我的产品坏了。这也是非常令人困惑的信息:它没有告诉我问题在哪里。它告诉我的是症状,而不是根本原因。

然而,DoSomething的单元测试是绿色的,因为它使用了一个假的日志,被构建为永不崩溃。是的,这显然是在撒谎。它传达了一个坏掉的功能是有效的。它如何有用?

(如果DoSomething()的单元测试失败,请确认:[Do your job with someInput]有一些错误。)

假设这是一个有破碎类的系统:

一个bug就会破坏几个特性,几个集成测试就会失败。

另一方面,相同的错误只会破坏一个单元测试。

现在,比较这两种情况。

同样的错误只会破坏一个单元测试。

使用坏日志的所有功能都是红色的 所有的单元测试都是绿色的,只有Log的单元测试是红色的

实际上,所有使用坏特性的模块的单元测试都是绿色的,因为通过使用模拟,它们删除了依赖项。换句话说,它们运行在一个理想的、完全虚构的世界里。这是唯一隔离和寻找虫子的方法。单元测试意味着模拟。如果你不嘲讽,你就不是单元测试。

的区别

集成测试会告诉你什么是不正常的。但在猜测问题可能在哪里时,它们毫无用处。

单元测试是唯一能告诉您错误确切位置的测试。要绘制此信息,他们必须在模拟环境中运行该方法,在该环境中所有其他依赖项都应该正确工作。

这就是为什么我认为你的句子“或者它只是一个跨越2个类的单元测试”在某种程度上被取代了。一个单元测试不应该跨越2个类。

这个回答基本上是我在这里写的一个总结:单元测试说谎,这就是我喜欢它们的原因。