集成和单元测试有什么区别?

时间:2008-08-14 06:27:57

标签: unit-testing integration-testing

我知道所谓的单元测试和集成测试的教科书定义。我很好奇的是,什么时候编写单元测试...我会写它们以涵盖尽可能多的类。

例如,如果我有一个Word类,我会为Word类编写一些单元测试。然后,我开始编写Sentence类,当需要与Word类进行交互时,我会经常编写单元测试,以便测试Sentence和{{1}至少在他们互动的地方。

让这些测试基本上成为集成测试,因为他们现在测试这两个类的集成,还是只是跨越2个类的单元测试?

一般来说,由于这种不确定的界限,我很少会真正编写集成测试...或者我使用成品来查看所有部分是否正常工作实际的集成测试,即使它们是手动的并且很少重复超出每个功能的范围?

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


修改

感谢大家的所有好评!我认为从各种各样的答案可以清楚地看出,单元测试和集成测试之间的界限肯定是模糊不清的,也许尝试找出哪些是真正的焦点应该保留在代码上有点迂腐(谢谢@Rob Cooper )。此外,对不起,但我不会接受任何答案,因为太多太好了,而且看起来非常主观。

20 个答案:

答案 0 :(得分:274)

对我来说,关键的区别在于集成测试会显示某项功能是否有效或被破坏,因为它们会在接近现实的情况下强调代码。他们调用一个或多个软件方法或功能,并测试它们是否按预期运行。

相反,测试单个方法的单元测试依赖于(通常是错误的)假设其他软件正常工作,因为它明确地模仿了每个依赖项。

因此,当实现某个功能的方法的单元测试为绿色时,表示该功能正在运行。

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

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

DoSomething对您的客户非常重要:这是一项功能,唯一重要的事情。这就是为什么你经常写一个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规范将会找到它并且您的端到端测试将失败。

该功能无效,因为Log已被破坏,而不是因为[Do your job with someInput]没有完成其工作。顺便说一句,[Do your job with someInput]是该方法的唯一责任。

此外,假设在100个其他功能中使用Log,在100个其他类别的100个其他方法中使用DoSomething

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

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

然而,Log的单元测试是绿色的,因为它使用的是假的DoSomething(),它永远不会破坏。并且,是的:它显然在说谎。它正在传达一个破碎的功能正在发挥作用。它有什么用?

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

假设这是一个类破坏的系统: A system with a broken class

单个错误会破坏多个功能,并且几个集成测试将失败。

A single bug will break several features, and several integration tests will fail

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

The same bug will break just one unit test

现在,比较两种情况。

同样的错误只能打破一个单元测试。

  • 使用损坏的Log的所有功能都是红色
  • 您的所有单元测试均为绿色,只有{{1}}的单元测试为红色

实际上,使用损坏功能的所有模块的单元测试都是绿色的,因为通过使用模拟,它们会删除依赖项。换句话说,它们运行在一个理想的,完全虚构的世界中。这是隔离bug并寻找它们的唯一方法。单元测试意味着嘲笑。如果你没有嘲笑,那么你不是单元测试。

差异

集成测试告诉什么不起作用。但它们在猜测问题可能在哪里

是没有用的。

单元测试是唯一的测试,告诉你哪里确切地说是bug。要绘制此信息,他们必须在模拟环境中运行该方法,其中所有其他依赖项应该正确工作。

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

这个回复基本上是我在这里写的内容摘要:Unit tests lie, that's why I love them

答案 1 :(得分:61)

当我编写单元测试时,我通过模拟依赖项将正在测试的代码的范围限制为我当前正在编写的类。如果我正在写一个Sentence类,而Sentence依赖于Word,我将使用一个模拟Word。通过模拟Word我可以只关注它的界面并测试我的Sentence类的各种行为,因为它与Word的界面交互。这样我只测试Sentence的行为和实现,而不是同时测试Word的实现。

一旦我编写单元测试以确保Sentence在基于Word的界面与Word交互时表现正常,那么我编写集成测试以确保我对交互的假设是正确的。为此,我提供了实际的对象并编写了一个测试,该测试演练了一个最终同时使用Sentence和Word的功能。

答案 2 :(得分:43)

我的10位:D

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

集成测试是下一步,它采用一个或多个组件并确保它们按照应有的方式协同工作。然后,您就会担心如何组件单独工作,但当你在 HtmlEditControl 中输入html时,它会以某种方式神奇地知道它的有效性与否......

虽然它是一条真正的可移动线..我宁愿更专注于让该死的代码完全停止^ _ ^

答案 3 :(得分:22)

单元测试使用模拟

您所谈论的是集成测试,它实际上测试了系统的整体集成。但是当你进行单元测试时,你应该分别测试每个单元。其他一切都应该被嘲笑。因此,对于Sentence类的情况,如果它使用Word类,则应该模拟您的Word类。这样,您只会测试Sentence类功能。

答案 4 :(得分:17)

我认为,当您开始考虑集成测试时,您更多地是在物理层而不是逻辑层之间进行交叉。

例如,如果您的测试涉及生成内容,那么它就是一个单元测试:如果您的测试只关注写入磁盘,那么它仍然是一个单元测试,但是一旦您测试了I / O和内容该文件,然后你自己进行集成测试。当您在服务中测试函数的输出时,它是一个单元测试,但是一旦您进行服务调用并查看函数结果是否相同,那么这就是集成测试。

从技术上讲,无论如何你都无法对单级测试进行单元测试。如果您的班级由其他几个班级组成,该怎么办?这会自动使其成为集成测试吗?我不这么认为。

答案 5 :(得分:12)

采用单一责任设计,其黑白两色。超过1个责任,它是一个集成测试。

通过鸭子测试(看起来,嘎嘎叫,蹒跚,它的鸭子),它只是一个单元测试,其中包含一个以上的新对象。

当您进入mvc并进行测试时,控制器测试始终是集成,因为控制器包含模型单元和视图单元。在该模型中测试逻辑,我会称之为单元测试。

答案 6 :(得分:10)

测试的性质

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

许多模块的集成测试是一项测试,它预计模块之间的合作会产生问题,因此单独使用单元测试很难找到这些问题

用以下术语考虑测试的性质:

  • 降低风险:这是测试的目的。只有单元测试和集成测试的组合才能完全降低风险,因为一方面单元测试本身不能测试模块之间的正确交互,另一方面集成测试可以运行功能一个非平凡的模块只是在很小程度上。
  • 测试编写工作:集成测试可以节省工作量,因为您可能不需要编写存根/假货/模拟。但是,在实现(和维护!)时,单元测试也可以节省工作量。这些存根/假货/模拟比配置没有它们的测试设置更容易。
  • 测试执行延迟:涉及重量级操作的集成测试(例如访问外部系统(如DB或远程服务器))往往很慢(呃)。这意味着可以更频繁地执行单元测试,这可以减少调试工作(如果有任何失败),因为您可以更好地了解您在此期间所做的更改。如果您使用测试驱动开发(TDD),这一点就变得尤为重要。
  • 调试工作:如果集成测试失败,但没有单元测试,那可能会非常不方便,因为涉及的代码太多,可能包含问题。如果您之前只更改了几行,这不是一个大问题 - 但是由于集成测试运行缓慢,您可能在如此短的时间间隔内运行它们......

请记住,集成测试可能仍然存根/伪造/模拟其依赖项的某些。 这为单元测试和系统测试(最全面的集成测试,测试所有系统)提供了充足的中间地带。

使用两者的实用方法

因此,一种务实的方法是:灵活地依靠集成测试,尽可能明智地使用集成测试,并使用单元测试,因为这样做风险太大或不方便。 这种思维方式可能比单元测试和集成测试的一些教条歧视更有用。

答案 7 :(得分:8)

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

是因为单元测试是你做的事情而集成测试是你不做的事情吗?或相反亦然?当然不是,你应该尝试两者兼顾。

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

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

是否因为单元测试测试一个单元并且集成测试测试了多个单元?当然不是。实际重要性是什么?无论如何,关于测试范围的理论讨论在实践中都会中断,因为术语“单元”完全取决于上下文。在类级别,单元可能是一种方法。在程序集级别,单元可能是一个类,在服务级别,单元可能是一个组件。 甚至类使用其他类,所以哪个是单位?

这并不重要。

测试很重要,F.I.R.S.T很重要,对定义的分析是浪费时间,只会让新手感到困惑。

答案 8 :(得分:4)

我认为我仍然会将一些交互类称为单元测试,前提是class1的单元测试正在测试class1的功能,而class2的单元测试正在测试它的功能,并且它们没有访问数据库。

当测试运行在我的大部分堆栈中时,我将测试称为集成测试,甚至命中数据库。

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

答案 9 :(得分:4)

我也这样做 - 我把它们称为所有单元测试,但在某些时候我有一个“单元测试”,涵盖了很多我经常将它重命名为“..IntegrationTest” - 只是一个名称更改,没有其他更改。

我认为从“原子测试”(测试一个小类或方法)到单元测试(类级别)和集成测试 - 然后是功能测试(通常覆盖了更多的东西)的延续自上而下) - 似乎没有一个干净的切断。

如果您的测试设置数据,并且可能加载数据库/文件等,那么可能更多的是集成测试(我发现集成测试使用更少的模拟和更多真实的类,但这并不意味着您不能嘲笑一些系统。)

答案 10 :(得分:4)

Unit Testing是一种测试方法,用于验证各个源代码单元是否正常工作。

Integration Testing是软件测试阶段,其中各个软件模块组合在一起并作为一组进行测试。

Wikipedia将单元定义为应用程序中最小的可测试部分,在Java / C#中是一种方法。但是在你的Word和Sentence类的例子中,我可能只是编写句子的测试,因为我可能会发现使用mock词类来测试句子类是过分的。因此,句子将是我的单位,而单词是该单位的实施细节。

答案 11 :(得分:4)

集成测试:测试数据库持久性 单元测试:模拟数据库访问。代码方法经过测试。

答案 12 :(得分:3)

如果您愿意,单元测试是针对一个工作单元或一段代码进行测试。通常由单个开发人员执行。

集成测试是指当开发人员将其代码提交到源控制存储库时,最好在集成服务器上执行的测试。集成测试可能由Cruise Control等实用程序执行。

因此,您进行单元测试以验证您构建的工作单元是否正常工作,然后集成测试验证您添加到存储库的任何内容都没有破坏其他内容。

答案 13 :(得分:2)

我称单元测试那些测试白盒测试一个类。类需要的任何依赖项都被伪造的(模拟)替换。

集成测试是指同时测试多个类及其交互的测试。在这些情况下,只有一些依赖项被伪造/嘲笑。

我不会调用Controller的集成测试,除非他们的一个依赖是真实的(即没有伪造)(例如IFormsAuthentication)。

分离这两种类型的测试对于在不同级别测试系统很有用。此外,集成测试往往是长期存在的,单元测试应该很快。执行速度的区别意味着它们的执行方式不同。在我们的开发过程中,单元测试在签到时运行(很好,因为它们非常快),集成测试每天运行一次/两次。我尝试尽可能多地运行集成测试,但通常会访问数据库/写入文件/使rpc的/等速度变慢。

这提出了另一个重点,单元测试应该避免命中IO(例如磁盘,网络,数据库)。否则他们会慢下来。设计这些IO依赖关系需要花费一些精力 - 我不能承认我一直忠实于“单元测试必须快速”的规则,但如果你这样做,那么在更大的系统上的好处会很快显现出来

答案 14 :(得分:2)

类比的简单解释

上面的例子做得很好,我不需要重复它们。因此,我将专注于使用示例来帮助您理解。

集成测试

集成测试检查一切是否一致。想象一下,一系列齿轮在手表中一起工作。整合测试将是:手表是否正确时间?是否仍然在3天内告知正确的时间?

它告诉你的是整体作品是否正常工作。如果它失败了:它没有告诉你它到底失败的确切位置。

单元测试

这些都是特定类型的测试。他们告诉你一件具体的事情是否有效。这种类型的测试的关键是它只测试一个特定的东西,同时假设其他一切工作正常。这是关键点。

示例: 让我们用一个例子详细说明这一点:

  • 我们以汽车为例。
  • 整合测试汽车:例如车开到Woop Woop后面了吗?如果它这样做,你可以放心地说汽车正在从整体的角度来看。这是一个集成测试。如果它失败了你不知道它实际上在哪里失败:它是散热器,变速器,发动机还是化油器?你不知道。它可能是任何东西。
  • 汽车的
  • 单元测试:引擎是否正常工作?这项测试假设汽车中的其他一切都运转正常。这样,如果这个特定的单元测试失败:你可以非常确信问题在于引擎 - 所以你可以快速隔离并修复问题。

使用存根

  • 假设您的汽车集成测试失败。它没有成功驾驶到伊丘卡。问题出在哪里?

  • 现在让我们假设您的发动机使用特殊的燃油喷射系统,并且该发动机单元测试也失败了。换句话说,集成测试和引擎单元测试都失败了。那么问题出在哪里? (给自己10秒钟的时间来得到答案。)

  • 发动机或燃油喷射系统存在问题吗?

你在这看到问题了吗?你不知道究竟是什么失败了。如果你使用不同的外部依赖项,那么这10个中的每一个都可能导致问题 - 而且你不知道从哪里开始。这就是为什么单元测试使用存根来假设其他一切都正常工作的原因。

答案 15 :(得分:2)

在单元测试中,您测试隔离的每个部分: enter image description here

在集成测试中,您将测试系统的许多模块:

enter image description here

这是当您仅使用单元测试(通常两个窗口都在工作,不幸的是不能同时使用)时发生的情况:

enter image description here

来源: source1 source2

答案 16 :(得分:1)

这个问题有点学术,不是吗? ;-) 我的观点: 对我来说,集成测试是对整个部分的测试,而不是十个中的两个部分是否在一起。 我们的集成测试显示,如果主构建(包含40个项目)将成功。 对于这些项目,我们有大量的单元测试。 关于单元测试最重要的是,一个单元测试不能依赖于另一个单元测试。所以对我来说,如果它们是独立的,那么上面描述的测试都是单元测试。对于集成测试,这一点不必重要。

答案 17 :(得分:1)

此外,重要的是要记住单元测试和集成测试都可以使用,例如JUnit自动化和编写。 在JUnit集成测试中,可以使用org.junit.Assume类来测试环境元素(例如,数据库连接)或其他条件的可用性。

答案 18 :(得分:1)

  

这些测试实际上是否已成为集成测试,因为他们现在测试这两个类的集成?或者它只是一个跨越2个班级的单元测试?

我认为是和是。跨越2个类的单元测试成为集成测试。

你可以通过使用模拟实现测试Sentence类来避免它--MockWord类,当系统的那些部分足够大以便由不同的开发人员实现时,这很重要。在这种情况下,Word单独进行单元测试,Sentence在MockWord的帮助下进行单元测试,然后Sentence与Word进行集成测试。

可以遵循真正的差异 1)1,000,000个元素的阵列很容易进行单元测试并且工作正常。 2)BubbleSort可以在10个元素的模拟阵列上轻松进行单元测试,并且工作正常 3)集成测试表明事情并不那么好。

如果这些部分是由一个人开发的,那么单元测试BubbleSoft时很可能会发现问题,因为开发人员已经有了真正的数组而且他不需要模拟实现。

答案 19 :(得分:0)

如果您是TDD的纯粹主义者,请在编写生产代码之前先编写测试。当然,测试不会编译,因此您首先要使测试编译,然后使测试通过。

您可以使用单元测试来做到这一点,但是不能使用集成或验收测试来做到这一点。如果您尝试进行集成测试,则直到完成后,任何内容都无法编译!