代码之家  ›  专栏  ›  技术社区  ›  WW.

自动化单元测试与自动化集成测试的优缺点是什么?

  •  28
  • WW.  · 技术社区  · 17 年前

    最近,我们一直在向现有java应用程序添加自动化测试。

    我们所拥有的

    这些测试中的大多数是集成测试,其中可能包含一堆调用,如:-

    1. HTTP post到servlet中
    2. servlet验证请求并调用业务层
    3. 业务层通过hibernate等进行大量工作,并更新一些数据库表
    4. servlet生成一些XML,通过XSLT运行这些XML以生成响应HTML。

    我们还有一些较小的单元测试,用于检查单个方法调用。

    这些测试都是作为夜间(或临时)构建的一部分运行的。

    问题

    这看起来不错,因为我们正在检查系统的边界:一端是servlet请求/响应,另一端是数据库。如果这些都能工作,那么我们就可以自由地重构或处理中间的任何东西,并且对测试中的servlet继续工作有一定的信心。

    这种方法可能会遇到什么问题?

    我看不出在单个类上添加更多的单元测试会有什么帮助。这会不会让重构变得更困难,因为我们更可能需要扔掉并重新编写测试?

    12 回复  |  直到 17 年前
        1
  •  34
  •   Charlie Martin    17 年前

    单元测试更紧密地定位故障。集成级测试更符合用户需求,因此更好地预测交付成功。除非建造和维护,否则它们都不是很好,但如果使用得当,它们都是非常有价值的。


    (更多…)

    单元测试的问题是,没有一个集成级测试能够像一组好的单元测试那样,尽可能多地执行所有代码。是的,这可能意味着您必须在某种程度上重构测试,但一般来说,您的测试不应该如此依赖于内部。比如说,你有一个函数,可以得到二的幂。你描述它(作为一个正式方法的家伙,我会要求你指定它)

    long pow2(int p); // returns 2^p for 0 <= p <= 30
    

    assertEqual(1073741824,pow2(30);
    assertEqual(1, pow2(0));
    assertException(domainError, pow2(-1));
    assertException(domainError, pow2(31));
    

    sizeof(long) 只保证不低于 sizeof(short) )那么这个测试很快就会失败。集成级测试可能会失败,但不一定会失败,而且它也很可能不会在计算 pow2(28) .

    关键是,他们真正测试的是不同的情况。如果您能够构建足够的细节和广泛的集成测试,您可能能够获得相同级别的覆盖率和细粒度测试,但这最多也很难做到,指数状态空间爆炸将击败您。通过使用单元测试对状态空间进行分区,您需要的测试数量的增长远小于指数增长。

        2
  •  29
  •   Davide    17 年前

    你在问两件事的利弊(骑马和骑摩托车的利弊是什么?)

    当然,这两种测试都是“自动测试”(~骑行测试),但这并不意味着它们是可选的(你不会在数百英里内骑马,也不会在靠近车辆泥泞的地方骑摩托车)


    单元测试 测试代码的最小单位,通常是一种方法。每个单元测试都与它所测试的方法密切相关,如果编写得很好,它(几乎)只与此相关。

    他们是伟大的向导 重构 指导

    重构呢,这似乎是你主要关心的问题?如果你只是重构一个方法的实现(内容),而不是它的存在或“外部行为”,那么单元测试仍然是有效的,并且非常有用(在你尝试之前你无法想象有多有用)。

    什么 什么 它不应该被实现细节(即。 怎样 方法应该做它需要做的事情)。


    自动化集成测试 测试代码的最大单元,通常是整个应用程序。

    它们非常适合测试 你不想用手测试的。但是您也可以进行手动集成测试,它们同样有效(只是不太方便)。


    今天开始一个新项目,没有单元测试是没有任何意义的,但是我要说的是,对于像您这样的现有项目来说,为您已经拥有并且正在工作的所有内容编写单元测试是没有太多意义的。

    在你的情况下,我宁愿采用“中间立场”的写作方法:

    1. 较小的集成测试,只测试要重构的部分。如果您正在重构整个过程,那么您可以使用当前的集成测试,但是如果您只重构——比如说——XML生成,那么要求数据库的存在就没有任何意义,因此我将编写一个简单而小的XML集成测试。
    2. 您将要编写的新代码的一系列单元测试。正如我在上面已经写过的,只要你“弄乱了中间的任何东西”,单元测试就会准备好,确保你的“弄乱”在某个地方。

    • 为什么它不起作用
    • 如果您对“混乱”的调试真的在修复某些东西

    只有在整个变更成功的情况下,集成测试才会在最后给出确认(在很长一段时间内,答案都是“否”)。在重构过程中,集成测试不会给您任何帮助,这将使重构变得更加困难,甚至可能令人沮丧。你需要进行单元测试。

        3
  •  20
  •   James Avery    17 年前

    我同意Charlie的观点,集成级测试更多地对应于用户操作和整个系统的正确性。我确实认为单元测试比更紧密地定位失败更有价值。单元测试在集成测试中提供两个主要值:

    1) 编写单元测试和测试一样是一种设计行为。如果您实践测试驱动开发/行为驱动开发,那么编写单元测试的行为可以帮助您准确地设计代码应该做什么。它帮助您编写更高质量的代码(因为松散耦合有助于测试),并帮助您编写足够的代码使您的测试通过(因为您的测试实际上就是您的规范)。

    2) 单元测试的第二个价值是,如果它们被正确地编写,它们会非常快。如果我对项目中的某个类进行了更改,是否可以运行所有相应的测试以查看我是否破坏了任何东西?我如何知道要运行哪些测试?需要多长时间?我可以保证它将比编写良好的单元测试更长。您最多应该能够在几分钟内运行所有单元测试。

        4
  •  16
  •   lavinio    17 年前

    • (+)保持测试接近相关代码
    • (+)很容易看出是否有人无意中更改了方法的行为
    • (-)为UI组件编写代码比为非GUI组件编写代码困难得多

    集成测试:

    • (+)在项目中有螺母和螺栓是很好的,但是集成测试确保它们相互适合
    • (-)更难定位错误源
    • (-)更难测试所有(甚至所有关键)代码路径

    理想情况下,两者都是必要的。

    示例:

    • 单元测试:确保输入索引>=0及<数组的长度。在界外时会发生什么?方法应该抛出异常还是返回null?

    • 集成测试:当输入负库存值时,用户看到了什么?

    我们发现,单元测试最好的一点是,它使开发人员从代码开始->测试->思考到思考->测试->密码如果开发人员必须首先编写测试,他往往会更多地考虑可能出现的问题。

    为了回答您的最后一个问题,由于单元测试离代码非常近,并迫使开发人员提前考虑更多问题,在实践中,我们发现,我们不太倾向于重构代码,因此移动的代码更少,因此不断地抛出和编写新的测试似乎不是问题。

        5
  •  5
  •   yetanotherdave    17 年前

    这个问题当然有庸俗的一面,但也指向了务实的考虑。

    测试驱动设计作为成为一个更好的开发人员的手段有其优点,但这不是必需的。许多优秀的程序员从来没有编写过单元测试。进行单元测试的最佳理由是它们在重构时给您带来的能力,特别是当许多人同时更改源代码时。在签入时发现bug对于项目来说也是一个巨大的时间节约(考虑转移到CI模型,并在签入而不是夜间构建)。因此,如果您在编写单元测试所测试的代码之前或之后编写单元测试,那么您就可以确定您所编写的新代码。单元测试所要确保的是以后代码可能发生的事情——这可能非常重要。单元测试可以在tehy进入QA之前阻止bug,从而加快项目的进度。

    集成测试强调堆栈中元素之间的接口(如果操作正确)。根据我的经验,集成是项目中最不可预测的部分。让单个部件正常工作并不是那么难,但由于在这一步中可能会出现各种类型的错误,因此将所有部件组合在一起可能非常困难。在许多情况下,由于集成过程中发生的事情,项目会延迟。在这一步中遇到的一些错误是在接口中发现的,这些接口由于一方所做的某些更改而被破坏,而这些更改没有传达给另一方。集成错误的另一个来源是在dev中发现的配置,但在应用程序转到QA时已被遗忘。集成测试有助于显著减少这两种类型。

    每种测试类型都旨在暴露开发阶段不同集成级别的问题,以节省时间。单元测试驱动了许多开发人员在一个存储库上操作的输出的集成。集成测试(名称不详)推动堆栈中组件的集成——组件通常由单独的团队编写。集成测试暴露的这类问题修复起来通常更耗时。

    因此,从实用角度讲,它实际上可以归结为您自己的组织/流程中最需要速度的地方。

        6
  •  3
  •   Matthew Farwell    17 年前

    单元测试和集成测试的区别在于测试运行所需的部件数量。

    单元测试(理论上)需要运行非常(或没有)的其他部件。 集成测试(理论上)需要运行大量(或全部)其他部件。

    集成测试测试行为和基础架构。单元测试通常只测试行为。

    那么,为什么要进行单元测试呢?

    例如,积分测试时很难测试边界条件。示例:后端函数需要正整数或0,前端不允许输入负整数,当您向后端函数传递负整数时,如何确保后端函数正常工作?可能正确的行为是抛出异常。集成测试很难做到这一点。

    因此,为此,您需要(对函数)进行单元测试。

    来自HTTP客户端的调用 servlet验证 从servlet到业务层的调用 数据库读取(hibernate) 业务层的数据转换 数据库写入(hibernate) XSLT转换->HTML HTML的传输->客户

    您需要单元测试和集成测试。

        7
  •  2
  •   Phil M    17 年前

    集成测试将包括您的应用程序和第三方软件的其他组件(例如,您的Oracle dev数据库,或针对webapp的Selenium测试)。这些测试可能仍然非常快,并且作为连续构建的一部分运行,但是由于它们注入了额外的依赖项,因此它们也有注入导致问题的新bug的风险 对于 您的代码,但不是由 通过 你的密码。最好,集成测试也可以在其中注入真实/记录的数据,并在给定这些输入的情况下断言应用程序堆栈作为一个整体的行为符合预期。

    问题归结为您希望找到什么样的bug,以及希望以多快的速度找到它们。单元测试有助于减少“简单”错误的数量,而集成测试有助于找出体系结构和集成问题,有望模拟Murphy定律对整个应用程序的影响。

        8
  •  2
  •   Roman    17 年前

    JoelSpolsky写了一篇非常有趣的关于单元测试的文章(这是Joel和其他人之间的对话)。

    单元测试的问题在于,当您想要更改应用程序的体系结构时,您必须更改所有相应的单元测试。而且这将花费很多时间(可能比重构本身花费更多的时间)。在所有这些工作之后,只有很少的测试会失败。

    我如何使用单元测试:我不喜欢TDD,所以我先编写代码,然后测试它(使用控制台或浏览器),以确保这些代码能够正常工作。只有在这之后,我添加了“棘手的”测试——其中50%在第一次测试后失败。

    它很有效,不需要太多时间。

        9
  •  2
  •   bh213    17 年前

    我们的项目中有4种不同类型的测试:

    1. 必要时进行模拟的单元测试
    2. 我们的逻辑是通过REST公开的,所以我们有一些测试可以执行HTTP

    我喜欢单元测试。它们运行速度非常快(比4次测试快100-1000倍)。它们是类型安全的,因此重构非常容易(使用良好的IDE)。

    主要问题是要做多少工作才能把它们做好。您必须模拟一切:数据库访问、网络访问和其他组件。你必须装饰不可修改的类,得到大量无用的类。您必须使用DI,以便您的组件不会紧密耦合,因此不可测试(请注意,使用DI实际上并不是一个缺点:)

    #3,尤其是#4更成问题。它们需要构建服务器上的一些生产环境子集。您必须构建、部署并运行应用程序。每次都必须有一个干净的DB。但最终,这是值得的。Watin测试需要不断的工作,但也需要不断的测试。我们在每次提交时都会运行测试,很容易看到我们何时破坏了某些内容。

    那么,回到你的问题上来。单元测试速度快(这一点非常重要,构建时间应该少于10分钟),并且易于重构。如果你的设计发生了变化,这比重写整个watin的东西要容易得多。如果你使用一个好的编辑器 查找用法

    通过REST/HTTP测试,您可以很好地验证系统是否实际工作。但是测试运行缓慢,因此很难在这个级别上进行完整的验证。我假设您的方法接受多个参数,或者可能接受XML输入。要检查XML中的每个节点或每个参数,需要数十次或数百次调用。您可以通过单元测试来实现这一点,但不能通过REST调用来实现,因为每个调用都需要很长的时间。

    我们的单元测试检查特殊边界条件的频率远远高于#3测试。他们(3)检查主要功能是否正常工作,仅此而已。这似乎对我们很有效。

        10
  •  2
  •   Carl Manaster    17 年前

    正如许多人提到的,集成测试将告诉您系统是否工作,单元测试将告诉您系统在哪里不工作。严格地说,从测试的角度来看,这两种测试是相辅相成的。

    我看不出怎么能再多加一堆 对单个类的单元测试将 重构,因为我们更有可能

    不会。这将使重构变得更容易、更好,并且更清楚地看到哪些重构是合适的和相关的。这就是为什么我们说TDD是关于设计的,而不是关于测试的。对于我来说,为一个方法编写一个测试是很常见的,在计算如何表达该方法的结果时,我会根据被测试类的其他一些方法,给出一个非常简单的实现。这种实现经常会进入被测试的类中。更简单、更可靠的实现、更清晰的边界、更小的方法:TDD——特别是单元测试——引导您朝着这个方向前进,而集成测试却没有。它们都很重要,都很有用,但它们的用途不同。

        11
  •  1
  •   Mark Simpson    17 年前

    尽管您描述的设置听起来不错,但单元测试也提供了一些重要的功能。单元测试提供了精细的粒度级别。通过松耦合和依赖注入,您几乎可以测试每个重要案例。您可以确保这些单元是健壮的;您可以使用大量输入或在集成测试期间不一定发生的有趣事情来仔细检查各个方法。

        12
  •  1
  •   Peter O. Manuel Pinto    5 年前

    你可能会对我感兴趣 this question and the related answers 也在这里,你可以找到我对已经给出的答案的补充。