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


307

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

例如,如果我有一个Word课程,我将为该Word课程编写一些单元测试。然后,我开始编写Sentence类,并在需要与Word该类进行交互时,经常编写我的单元测试,以便它们同时对... SentenceWord...至少在它们进行交互的位置进行测试。

这些测试是否实质上已经成为集成测试,因为它们现在测试这两个类的集成,还是仅仅是跨越两个类的单元测试?

总的来说,由于这条线的不确定性,我很少实际编写集成测试……或者是我使用最终产品来查看所有部件在实际的集成测试中是否都能正常工作,即使它们是手动的并且很少超出范围每个特征?

我是误解了集成测试,还是集成测试和单元测试之间的区别真的很小?

Answers:


300

对我而言,主要区别在于集成测试可以揭示某个功能是否正常运行或已损坏,因为它们会在接近现实的情况下强调代码。他们调用一种或多种软件方法或功能,并测试它们是否按预期方式工作。

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

因此,当实现一些功能的方法的单元测试是绿色的,它并不能意味着功能工作正常。

假设您在某处有这样的方法:

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;
}

您可以使用依赖注入,某种工厂方法或任何Mock框架,或者只是扩展被测类来实现。

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

该功能不起作用,是因为它Log已损坏,而不是因为[Do your job with someInput]它没有在做它的工作。顺便说一下,这[Do your job with someInput]是该方法的唯一责任。

另外,假设Log在100个其他类的100个其他方法中使用了100个其他功能。

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

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

但是,DoSomething单元测试是绿色的,因为它使用的是伪造的Log,永不中断。而且,是的:它显然在说谎。它正在传达损坏的功能正在起作用。有什么用?

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

假设这是一个类损坏的系统: 类别损坏的系统

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

一个错误将破坏多个功能,并且多个集成测试将失败

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

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

现在,比较两种情况。

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

  • 您所有使用断线的功能Log都为红色
  • 您所有的单元测试都是绿色的,只有单元测试Log是红色的

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

区别

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

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

这就是为什么我认为您的句子“或者仅仅是跨越2个类的单元测试”以某种方式被取代的原因。单元测试不得跨越2个类。

这个答复基本上是我在这里写的摘要:单元测试的谎言,这就是为什么我喜欢它们


6
一个非常好的答案!但是,我只想补充一点,模拟并非仅用于单元测试。在许多集成测试案例中,它也可能非常有用。
n.Stenvang,2014年

1
好答案!我只是不同意两点:1)集成测试“在猜测问题可能出在哪里没有用”。和2)“单元测试不得跨越2个类”。我创建了许多集成测试,一旦它们破裂,通常不难发现问题的根源,只要获得完整的堆栈跟踪或单个失败的断言即可(在这种情况下,很难找到源头,但是集成测试为调试提供了一个包含的上下文)。(继续)
罗热

5
单元测试可以行使多个课程,但前提是它们不是应该具有自己单独的单元测试的公共课程。一种情况是,经过公共测试的班级使用其他仅支持公共班级的非公共帮助程序班级;在这种情况下,“单元”包括两个或多个类。另一种情况是,大多数类使用第三方类(String / string类,集合类等),这些第三方类被嘲笑或与它们隔离是没有道理的;我们只是将它们视为稳定可靠的依赖关系,而这些依赖关系不在测试范围之内。
罗杰里奥

2
使用集成测试,要找到根本问题要难一些,但是您仍然可以对其进行调试并找到根本问题。假设单元测试不会经常失败,那么,如果只有集成测试,也许很少会花更多的时间来修复错误,但是这样您也可以获得测试组件集成的附加值,从而节省了编写时间单元测试。我认为这个说法(来自我的老板)是错误的,但是我不确定我如何说服他,有什么想法?
BornToCode 2015年

1
通过这个答案的理由,人们可以辩称,跳过编写单元测试并花费时间通过查找失败的集成测试的源来节省时间可能更有效。

62

当我编写单元测试时,我通过模拟依赖关系将测试代码的范围限制为当前正在编写的类。如果我正在编写Sentence类,并且Sentence对Word有依赖性,那么我将使用模拟Word。通过模拟Word,我只能专注于其接口,并测试Sentence类与Word的接口交互时的各种行为。这样,我仅测试句子的行为和实现,而不同时测试Word的实现。

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


43

我的10位:D

经常有人告诉我,单元测试是对单个组件的测试-应该充分利用它。现在,这往往有很多层次,因为大多数组件都是由较小的零件制成的。对我来说,单元是系统的功能部分。因此,它必须提供一些有价值的东西(即,不是用于字符串解析的方法,而是可能是HtmlSanitizer)。

集成测试是下一个步骤时,其采取的一个或多个组件,并确保他们一起工作,因为他们应该。你是那么担心关于上述错综复杂如何组件独立工作,但是当你输入HTML到您的HtmlEditControl,它以某种方式神奇地知道它是否有效。

它实际上是一条可移动的线..我宁愿将精力更多地放在使该死的代码上使之停止工作^ _ ^


23

单元测试使用模拟

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


我知道这是一篇旧文章,但我只是偶然发现。如果您有一个名为Font的第三类,与Sentence类进行交互,并且您想测试Word和Sentence类之间的功能,那您就必须模拟Font类,但这不会使其成为单元测试。因此,我要说的是,使用模拟不一定会使它成为单元测试,模拟也可以用于集成测试。
n.Stenvang 2014年

2
当然,可以在集成测试中使用模拟,但是为了使单元测试实际如此,应该对单元外部的所有东西都进行模拟。如果集成测试使用模拟,那么它们很可能是部分集成测试(如果存在该术语)。当然,这是部分自上而下或自下而上的集成测试。后一种通常不需要模拟,而前者则需要。
罗伯特·科里特尼克

17

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

例如,如果您的测试涉及生成内容,那么它就是一个单元测试:如果您的测试仅涉及写入磁盘,那么它仍然是一个单元测试,但是一旦测试了I / O和文件的内容,那么您就需要进行集成测试。在服务中测试功能的输出时,这是一个单元测试,但是一旦进行服务调用,然后查看功能结果是否相同,那就是集成测试。

从技术上讲,您无论如何不能只对一个类进行单元测试。如果您的课程由其他几个课程组成,该怎么办?这会自动使它成为集成测试吗?我不这么认为。


8
“从技术上讲,无论如何,您不能对一个课程进行单元测试。如果您的课程由其他几个课程组成,该怎么办?” 好吧,“严格”单元测试只会模拟/存根所有依赖项。但是,这是否总是可行的值得商...……
sleske 2010年

2
确实如此-重要的一点是能够将依赖关系保持在最低限度。
乔恩·林贾普

-1,单元测试不是测试单个功能,而是单个软件功能或类,即它测试软件的逻辑单元。
CharlesB 2013年

12

采用单一职责设计,黑色和白色。1个以上的职责,它是一个集成测试。

通过鸭子测试(外观,嘎嘎,蹒跚,鸭子),它只是一个单元测试,其中包含多个新对象。

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


10

测试的性质

单元测试模块的X是一个测试,期望(以及用于检查)仅在模块X.问题

许多模块的集成测试是一种预期会由于模块之间的协作而产生问题的测试,因此仅使用单元测试就很难发现这些问题。

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

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

请记住,集成测试可能仍会存根/伪造/模拟掉其某些依赖性。这在单元测试和系统测试(最全面的集成测试,测试整个系统)之间提供了足够的中间立场。

两者兼顾的务实方法

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


10

在单元测试中,您测试隔离的每个部分: 在此处输入图片说明

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

在此处输入图片说明

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

在此处输入图片说明

来源: 源1 源2


您有三个图像,但只有两个来源。
gerrit

1
@gerrit看看第一个消息源-那里有两张照片
Michu93 '19

1
爱这个答案👏
Dzenis H.

8

我认为答案是“为什么重要?”

是因为单元测试是您要做的,集成测试是您不需要的吗?或相反亦然?当然不是,您应该尝试同时做这两项。

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

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

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

这并不重要。

测试很重要,FIRST很重要,在定义上乱七八糟是浪费时间,这只会使新手感到困惑。


5
-1定义使人们能够使用相同的术语而不必总是解释它们的含义,这对协作至关重要。因此,必须了解这两种概念之间的区别。
CharlesB

正如@CharlesB提到的那样,这一点很重要,因此无需每次都进行解释或发现每个人都有不同的定义而引起混乱。测试将以不同的方式编写并以不同的方式运行,但这并不意味着不应通过定义差异来完成两者。
Shane

答案的结论可能是极端的,但它的大部分点都相当有效:单元测试和集成测试大致相同的事情,除了自己的粒度 -这不是很明显,其中一条线应该在它们之间绘制。
Lutz Prechelt

在专业环境中创建通用语言时,这无济于事。多数情况下,您是对的,但没关系,没有通用语言会在团队中造成误解和混乱。我认为最好的选择是让团队同意他们的术语和定义。
user924272'2

4

我想我仍然将几个交互的类称为单元测试,条件是class1的单元测试正在测试class1的功能,class2的单元测试正在测试其功能,并且它们没有触及数据库。

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

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


4

我做同样的事情-我将它们称为所有单元测试,但是在某个时候,我有一个涵盖了很多内容的“单元测试”,因此我经常将其重命名为“ ..IntegrationTest”-只是名称更改,其他都没有更改。

我认为这是从“原子测试”(测试一个微小的类或方法)到单元测试(类级别)和集成测试的延续,然后是功能测试(通常从上到下涵盖更多内容) -似乎没有明确的界限。

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


4

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

集成测试是软件测试的阶段,在此阶段中,各个软件模块被组合并作为一个整体进行测试。

Wikipedia将单位定义为应用程序中可测试的最小部分,在Java / C#中是一种方法。但是在您的单词和句子类的示例中,我可能只会编写句子的测试,因为使用模拟单词类来测试句子类可能会显得过于刻板。因此,句子将是我的单元,而单词是该单元的实现细节。


4

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


3

单元测试是针对工作单元或代码块(如果需要)进行测试。通常由单个开发人员执行。

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

因此,您进行单元测试以验证所构建的工作单元是否正常运行,然后集成测试将验证您添加到存储库中的任何内容都不会破​​坏其他功能。


2

我称单元测试为那些白盒测试为类的测试。类需要的所有依赖关系都将被伪造的(嘲笑)替换。

集成测试是同时测试多个类及其交互的测试。在这些情况下,只有某些依赖项是伪造/模拟的。

我不会调用Controller的集成测试,除非它们的依赖关系之一是真实的依赖关系(即,不是伪造的)(例如IFormsAuthentication)。

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

这就提出了另一个重要的观点,单元测试应该避免击中IO(例如磁盘,网络,数据库)。否则,它们会大大减慢速度。设计这些IO依赖项需要花费一些精力-我不能承认我一直忠实于“单元测试必须快速”的规则,但是如果您愿意的话,那么更大系统的好处就会很快显现出来。 。


2

用类推简单解释

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

整合测试

集成测试检查是否一切正常。想象一下,一系列嵌齿轮在手表中一起工作。集成测试将是:手表是否指示正确的时间?还可以告诉您3天内的正确时间吗?

它只告诉您整体是否正常。如果失败:它不会告诉您确切的失败位置。

单元测试

这些实际上是特定的测试类型。他们告诉您某件事是正常的还是失败的。此类测试的关键在于,它只测试一件特定的东西,同时假设其他所有东西都工作正常。这就是关键。

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

  • 让我们以汽车为例。
  • 汽车的集成测试:例如,汽车驶向Woop Woop并返回吗?如果这样做的话,您可以放心地说,从整体上看汽车正在运转。这是一个集成测试。如果它失败了,您根本不知道它实际上在哪里出故障:是散热器,变速箱,发动机还是化油器?你不知道。可能是任何东西。
  • 汽车的单元测试:发动机是否在工作?该测试假设汽车中的所有其他设备都工作正常。这样,如果该特定的单元测试失败了:您可以非常确定问题出在引擎上,从而可以快速隔离并解决问题。

使用存根

  • 假设您的汽车集成测试失败。它无法成功驱动到Echuca。问题出在哪里?

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

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

您在这里看到问题了吗?您不知道到底是什么在失败。如果您使用不同的外部依赖关系,那么这10个中的每一个都可能引起问题-并且您将不知道从哪里开始。这就是为什么单元测试使用存根假设其他所有东西都工作正常的原因。


1

这个问题有点学术,不是吗?;-)我的观点:对我而言,集成测试是整个部分的测试,而不是十分之二的测试。我们的集成测试表明,主构建(包含40个项目)能否成功。对于项目,我们有大量的单元测试。对我而言,与单元测试有关的最重要的事情是,一个单元测试一定不能依赖于另一个单元测试。因此,对我来说,您上面描述的两个测试都是独立的单元测试。对于集成测试,这并不重要。


1

由于这些测试现在测试了这两个类的集成,因此这些测试从本质上成为集成测试了吗?还是仅仅是跨越2个类的单元测试?

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

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

实际差异的示例可以如下:1)1,000,000个元素的数组很容易进行单元测试并且可以正常工作。2)BubbleSort可以很容易地在10个元素的模拟数组上进行单元测试,并且可以正常工作。3)集成测试表明有些事情不太好。

如果这些部分是由单个人开发的,则在单元测试BubbleSoft时会发现最可能出现的问题,这仅仅是因为开发人员已经拥有实际的数组并且他不需要模拟实现。


1

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


0

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

您可以使用单元测试来做到这一点,但是不能使用集成或验收测试来做到这一点。如果您尝试进行集成测试,那么在完成之前,什么都不会编译!

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.