首页 文章

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

提问于
浏览
266

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

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

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

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

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


编辑

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

19 回答

  • 1

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

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

  • 262

    对我来说,关键的区别在于 integration tests 揭示了一个功能是否有效或被破坏,因为它们会在接近现实的场景中强调代码 . 他们调用一个或多个软件方法或功能,并测试它们是否按预期运行 .

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

    因此,当实现某些功能的方法的单元测试为绿色时,它确实意味着该功能正在运行 .

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

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

    DoSomething 对您的客户非常重要:它's a feature, the only thing that matters. That'为什么您通常会写一个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
    

    毫无疑问:如果测试通过,你可以断言你正在提供一个有效的功能 . 这就是你可以称之为 Business Value 的东西 .

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

    在实践中,你做了类似的事情:

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

    您可以使用依赖注入,或某些工厂方法或任何Mock Framework来执行此操作,或者只是扩展测试中的类 .

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

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

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

    是的,100个功能将失败 . 但是,幸运的是,100次端到端测试也失败了,并揭示了这个问题 . 并且,是的: they are telling the truth .

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

    然而, DoSomething 's unit test is green, because it'使用假 Log ,构建永不休息 . 并且,是的: it's clearly lying . 它正在传达一个破碎的功能正在发挥作用 . 它有什么用?

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

    假设这是一个类损坏的系统:
    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 的所有功能都是红色的

    • 所有单元测试均为绿色,只有 Log 的单元测试为红色

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

    差异

    集成测试告诉 what 不起作用 . 但它们在_799313中没有用,问题可能就是这样 .

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

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

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

  • 3

    我的10位:D

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

    Integration Tests 是下一步,它采取一个或多个组件,并确保它们应该按照它们的方式一起工作 . 然后你就会担心组件如何单独工作,但是当你在HtmlEditControl中输入html时,它会以某种方式神奇地知道它的有效与否 .

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

  • 10

    Unit tests use mocks

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

  • 0

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

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

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

  • 8

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

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

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

  • 2

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

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

    • Test writing effort :集成测试可以节省工作量,因为您无需编写存根/伪造/模拟 . 但是,在实现(和维护!)时,单元测试也可以节省工作量 . 这些存根/假货/模拟比配置没有它们的测试设置更容易 .

    • Test execution delay :涉及重量级操作的集成测试(例如访问外部系统(如DB或远程服务器))往往很慢 . 这意味着可以更频繁地执行单元测试,这可以减少调试工作(如果有任何失败),因为您可以更好地了解您在此期间已更改的内容 . 如果您使用测试驱动开发(TDD),这一点就变得尤为重要 .

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

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

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

  • 1

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

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

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

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

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

    这并不重要 .

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

  • 9

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

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

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

  • 0

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

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

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

  • 16

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

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

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

  • 3

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

  • 59

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

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

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

  • 1

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

  • 0

    我打电话给单元测试那些白盒测试一个类的测试 . 类需要的任何依赖项都被伪造的(mocks)替换 .

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

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

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

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

  • 3

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

    Integration Tests

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

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

    Unit Tests

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

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

    • 让我们以汽车为例 .

    • Integration 测试汽车:例如Car开车到Echuca然后回来 . 如果它这样做,你可以放心地说汽车正在从整体的角度来看 . 这是一个集成测试 . 如果它失败了你不知道它实际上在哪里失败:它是散热器,变速器,发动机还是化油器?你不知道 . 它可能是任何东西 .

    • Unit test 对于汽车:发动机正在工作 . 这项测试假设汽车中的其他一切都运转正常 . 这样,如果这个特定的单元测试失败:您可以非常确信问题在于引擎 - 因此您可以快速隔离并解决问题 .

    The consequences of mixing your integration and unit tests:

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

    • 你看看你的引擎测试 . 但在这种特殊情况下,发动机测试使用外部依赖性:它使用特殊的燃料系统 . 让我们假设发动机单元测试也失败了 . 换句话说,集成测试和引擎单元测试都失败了 . 那么问题出在哪里? (给自己10秒钟的时间来得到答案 . )•发动机是否发生故障,或者是导致发动机失效的燃油系统?

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

    我希望这有帮助 .

  • 3

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

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

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

    真正的差异可以追随以下1)1,000,000个元素的阵列很容易进行单元测试并且工作正常 . 2)BubbleSort很容易在10个元素的模拟阵列上进行单元测试,并且工作正常3)集成测试表明某些东西不是很好 .

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

  • 20

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

  • 41

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

    您可以使用单元测试执行此操作,但不能使用集成或验收测试 . 如果您尝试使用集成测试,那么在您完成之前,任何事情都无法编译!

相关问题