ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

这些测试是否本质上成为集成测试,因为它们现在测试这 2 个类的集成,还是只是一个跨越 2 个类的单元测试?

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

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


7
7 revs, 6 users 85%

对我来说,关键的区别在于集成测试可以揭示一个功能是正常工作还是被破坏,因为它们在接近现实的场景中强调代码。他们调用一种或多种软件方法或功能并测试它们是否按预期运行。

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

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

假设您有这样的方法:

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] 有一些错误。)

https://i.stack.imgur.com/611ZY.jpg

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

https://i.stack.imgur.com/rKhSr.jpg

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

https://i.stack.imgur.com/M64Vb.jpg

现在,比较这两种情况。

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

您使用损坏日志的所有功能都是红色的

您所有的单元测试都是绿色的,只有 Log 的单元测试是红色的

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

区别

集成测试告诉我们什么是行不通的。但是它们对于猜测问题可能出在哪里是没有用的。

单元测试是唯一能告诉你错误到底在哪里的测试。要获取此信息,他们必须在模拟环境中运行该方法,其中所有其他依赖项都应该正确工作。

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

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


一个非常好的答案!但是我只想补充一点,模拟不仅仅用于单元测试。它在许多集成测试用例中也可能非常有用。
很好的答案!我只是不太同意两点:1)集成测试“对于猜测问题可能在哪里没有用”;和 2)“单元测试不应该跨越 2 个类”。我创建了许多集成测试,当它们中断时,通常不难查明问题的根源,只要您获得完整的堆栈跟踪或单个失败的断言(在这种情况下,可能更难找到源头,但是不是那么多,因为集成测试为调试提供了一个包含的上下文)。 (继续)
单元测试可以运行多个类,只要它们不是应该有自己单独的单元测试的公共类。一种情况是公共测试类使用其他仅支持公共类的非公共帮助类;在这种情况下,“单元”包括两个或多个类别。另一种情况是大多数类使用第三方类(字符串/字符串类、集合类等),这些类被模拟或隔离是没有意义的;我们只是将它们视为在测试范围之外的稳定可靠的依赖项。
通过集成测试,找到根本问题有点困难,但您仍然可以调试它并找到根本问题。假设单元测试不会经常失败,那么如果您只有集成测试,可能很少会花费更多时间来修复错误,但是您也会获得测试组件集成的附加价值,并且可以节省编写时间单元测试。我认为这个说法(来自我的老板)是错误的,但我不确定我该如何说服他,有什么想法吗?
根据这个答案中的推理,有人可能会争辩说,跳过编写单元测试并通过在失败时定位失败的集成测试的来源来节省时间可能会更有效。
S
ShellZero

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

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


R
Rob Cooper

我的 10 位 :D

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

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

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


M
Michu93

https://i.stack.imgur.com/bmhZg.jpg

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

https://i.stack.imgur.com/v186T.jpg

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

https://i.stack.imgur.com/yHGn1.gif

来源:source1 source2


您有三个图像,但只有两个来源。
@gerrit 看看第一个来源 - 两张图片来自那里
喜欢这个答案👏
整车撞墙的画面在我看来更像是系统测试
R
Robert Koritnik

单元测试使用模拟

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


我知道这是一个旧帖子,但我只是偶然发现了它。如果您有一个名为 Font 的第三个类与 Sentence 类交互,并且您想测试 Word 和 Sentence 类之间的功能,那么您必须模拟 Font 类,但这不会使其成为单元测试。所以我要说的是,使用模拟并不一定使它成为单元测试,模拟也可以用于集成测试。
当然,模拟可以在集成测试中使用,但是为了使单元测试实际上是这样的,应该模拟单元外部的所有内容。如果集成测试使用模拟,那么它们很可能是部分集成测试(如果存在这样的术语)。当然,也有自上而下或自下而上的部分集成测试。后者通常不需要模拟,而前者则需要。
J
Jon Limjap

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

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

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


“从技术上讲,无论如何你都不能只对一个班级进行单元测试。如果你的班级由其他几个班级组成怎么办?”好吧,“严格”的单元测试只会模拟/存根所有依赖项。然而,这是否总是实用的,值得商榷......
这是真的 sieske - 重要的是能够将依赖关系保持在绝对最低限度。
-1,单元测试不测试单个功能,而是单个软件功能或类,即它测试软件的逻辑单元。
D
DevelopingChris

采用单一职责设计,黑白分明。不止一个责任,它是一个集成测试。

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

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


C
Captain Man

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

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

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

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

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

这无关紧要。

测试很重要,FIRST 很重要,对定义争论不休是浪费时间,只会让新手对测试感到困惑。


-1 定义使人们能够使用相同的术语而不总是解释它们的含义,并且对于协作至关重要。因此,必须了解这两个概念之间的区别。
正如@CharlesB 所提到的,这很重要,因此无需每次都解释或发现每个人都有不同的定义而导致混淆。测试将以不同的方式编写并以不同的方式运行,但这并不意味着两者都不应该通过想要定义差异来完成。
答案的结论可能是极端的,但它的大部分观点都是非常有效的:单元测试和集成测试除了它们的粒度之外几乎是相同的——而且它们之间应该在哪里划清界限并不明显。
在专业环境中创建通用语言时,这无济于事。虽然大多数情况下,你是对的,但这并不重要,没有共同的语言会在团队中造成误解和混乱。我认为最好的选择是让团队就他们的术语和定义达成一致。
L
Lutz Prechelt

测试的性质

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

许多模块的集成测试是一种预期模块之间的协作会出现问题的测试,因此单独使用单元测试很难发现这些问题。

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

降低风险:这就是测试的目的。只有单元测试和集成测试的组合才能完全降低风险,因为一方面单元测试本身不能测试模块之间的正确交互,另一方面集成测试只能行使非平凡模块的功能在很小的程度上。

测试编写工作:集成测试可以节省工作量,因为您可能不需要编写存根/伪造/模拟。但是,在实现(和维护!)这些存根/伪造/模拟时,单元测试也可以节省工作量,这比在没有它们的情况下配置测试设置更容易。

测试执行延迟:涉及重量级操作(例如访问数据库或远程服务器等外部系统)的集成测试往往很慢(呃)。这意味着可以更频繁地执行单元测试,从而在任何失败时减少调试工作,因为您可以更好地了解在此期间所做的更改。如果您使用测试驱动开发 (TDD),这一点变得尤为重要。

调试工作:如果集成测试失败,但没有一个单元测试失败,这可能非常不方便,因为涉及的代码太多可能包含问题。如果您之前只更改了几行,这不是一个大问题——但由于集成测试运行缓慢,您可能没有在如此短的时间间隔内运行它们......

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

实用的方法来使用两者

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


L
Lance Fisher

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

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

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


M
Michael Neale

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

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

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


g
grom

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

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

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


C
CharlesB

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


C
Christian Hagelid

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

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

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


C
CVertex

我将那些白盒测试类的测试称为单元测试。类所需的任何依赖项都被替换为假的(模拟)。

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

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

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

这提出了另一个重要的观点,单元测试应该避免碰到 IO(例如磁盘、网络、数据库)。否则他们会慢很多。设计这些 IO 依赖项需要一些努力——我不能承认我一直忠实于“单元测试必须快速”的规则,但如果你这样做了,那么在更大系统上的好处很快就会变得明显.


B
BenKoshy

类比的简单解释

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

集成测试

集成测试检查一切是否协同工作。想象一下手表中的一系列齿轮一起工作。集成测试将是:手表显示的时间是否正确? 3天后它仍然告诉正确的时间吗?

它告诉你的只是整个作品是否有效。如果失败:它不会准确地告诉你失败的地方。

单元测试

这些确实是特定类型的测试。它们会告诉您一件特定的事情是有效还是失败。这种测试的关键是它只测试一个特定的东西,同时假设其他一切都很好。这是关键点。

示例:让我们通过一个示例来详细说明这一点:

让我们以汽车为例。

汽车的集成测试:例如,汽车是否开往 Woop Woop 并返回?如果它这样做,您可以肯定地说汽车从整体角度来看正在工作。这是一个集成测试。如果它失败了,你不知道它实际上在哪里失败:是散热器、变速箱、发动机还是化油器?你不知道。它可以是任何东西。

汽车的单元测试:引擎是否在工作?该测试假定汽车中的其他一切工作正常。这样,如果这个特定的单元测试失败:您可以非常确信问题出在引擎上——因此您可以快速隔离并修复问题。

使用存根

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

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

是发动机的问题,还是燃油喷射系统的问题?

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


J
John Smithers

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


P
Pavel Feldman

这些测试是否本质上成为集成测试,因为它们现在测试这两个类的集成?或者它只是一个跨越 2 个类的单元测试?

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

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

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

如果这些部分是单人开发的,在单元测试 BubbleSoft 的时候很可能会发现问题,因为开发者已经有了真正的数组,不需要 mock 实现。


P
Paulo Merson

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


S
StevePoling

如果您是 TDD 纯粹主义者,那么您会在编写生产代码之前编写测试。当然,测试不会编译,所以你先让测试编译,然后让测试通过。

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