不编写单元测试是合理的,因为它们往往会在以后被注释掉,或者因为集成测试更有价值?


35

我正在与一位同事讨论单元/集成测试,他提出了一个有趣的案例来反对编写单元测试。我是大型单元测试(主要是JUnit)的支持者,但是我很想听听其他人的看法,因为他提出了一些有趣的观点。

总结他的观点:

  • 当发生重大代码更改时(新的POJO集,主要应用程序重构等),单元测试往往被注释掉而不是重新编写。
  • 最好将时间花在涉及用例的集成测试上,这会使范围较小的测试变得不那么重要。

有这个想法吗?尽管集成测试听起来至少有价值,但我仍在进行单元测试(因为我一直认为它会产生改进的代码)。


9
您自己说了这句话:编写单元测试会生成更好的代码,因为这会迫使您编写可测试的代码。可单元测试的代码具有许多非常重要的属性,可以提高其整体质量。
罗伯特·哈维

1
@Robert Harvey-同意-我很想查看这些属性的列表。
杰夫·莱文,2015年

18
至于要点-您基本上是在说单元测试的一个缺点是,当您不使用它们时,它们将无法工作。您可以对其他练习说同样的话。同样,您对被动语音的使用掩盖了以下事实:有人正在积极地对单元测试进行注释。因此,看来您没有团队开发人员的支持,这是另一个问题。
JacquesB 2015年


Answers:


62

我倾向于支持您的朋友,因为单元测试经常会测试错误的东西

单元测试并非天生就不好。但是他们经常测试实现细节而不是输入/输出流程。当发生这种情况时,您最终将获得完全毫无意义的测试。我自己的规则是,好的单元测试会告诉您您刚破坏了某些内容;不良的单元测试只会告诉您您刚刚更改了某些内容。

我脑海中的一个例子是几年前被纳入WordPress的一项测试。被测试的功能围绕相互调用的过滤器进行,测试正在验证是否可以正确的顺序调用回调。但是测试不是(a)运行链以验证是否按预期顺序调用了回调,而是将测试重点放在(b)读取可能不应该公开的某些内部状态上。更改内部零件,并且(b)变为红色;而(a)仅在内部结构的更改超出预期结果时才变为红色。(b)在我看来显然是毫无意义的考验。

如果您有一个向外界公开一些方法的类,那么在我看来,要测试的正确方法是后一种方法。如果还测试内部逻辑,则最终可能会使用复杂的测试方法或一连串的单元测试将内部逻辑暴露给外界,而这些单元测试总是会在您要更改任何内容时中断。

话虽如此,如果您的朋友对您所建议的单元测试本身如此严格,我会感到惊讶。相反,我会认为他很务实。也就是说,他观察到实际上编写的单元测试几乎没有意义。引用:“单元测试往往被注释掉而不是重新编写”。对我来说,那里隐含着一个信息-如果他们倾向于重新加工,那是因为他们倾向于吮吸。假设是这样,第二个命题是:开发人员将花费更少的时间来编写更难出错的代码-即集成测试。

因此,这不是一个好坏。只是容易出错,实际上在实践中经常会出错。


13
这个这个一百倍 对于测试驱动的开发来说,这是一件很棒的事情。首先编写测试将帮助您编写测试,这些测试不是测试实现细节,而是要尝试实现的预期行为。
wirrbel 2015年

9
我认为脆弱的单元测试不是单元测试的固有特性,而是误解了单元测试的用途。我们这里需要的是对开发人员的更好的教育。如果您对测试实施细节进行单元化,那么您做错了。如果您公开私有方法以“为了测试它们”,那么您做错了。始终测试公共接口和行为,这种行为的更改频率应少于实现细节!
Andres F.

2
@DocBrown:确实不是。我的意思是,经常做错事,以至于OP的同事在大多数情况下都正确无误-至少在我遇到过单元测试困扰的公司的所有情况下,都正确无误。
Denis de Bernardy

3
@DenisdeBernardy:我的意思是:您写的所有内容在实践中得到的很多智慧当然是正确的,但是在“集成测试作为解决方案”的建议的背景下,我错过了一个结论,这对与他的同事一起的OP案意味着什么。更好的选择”。
布朗

5
我完全同意布朗博士的观点。从本质上讲,您的立场似乎是单元测试很好,但是如果编写不好,它们就会成为麻烦。因此,您完全不同意OP的同事,只是您应该确保在断言它们的有用性之前确实编写了单元测试。我认为您的答案非常令人困惑,因为第一句话表明您同意OP的同事,但事实并非如此。它看起来更像是关于单元测试编写不当的抱怨(我承认这在实践中是个问题),而不是一般情况下反对单元测试的案例。
文森特·萨瓦德

38

当发生重大代码更改时,单元测试倾向于被注释掉而不是重新编写。

一群杂乱无章的牛仔编码员认为,当您注释掉所有现有测试时,所有测试都变成“绿色”就可以满足了:当然。返工单元测试需要与第一手测试相同的纪律,所以您要做的是团队的“完成定义”。

最好将时间花费在涵盖用例的集成测试上,这会使范围较小的测试变得不那么重要。

这既不是完全错误也不是完全正确。编写有用的集成测试的工作很大程度上取决于您正在编写哪种软件。如果您拥有一个可以几乎像编写单元测试一样容易编写集成测试的软件,它们仍然运行很快,并且可以很好地覆盖您的代码,那么您的同事很有意义。但是对于许多系统,我已经看到集成测试无法轻松实现这三点,这就是为什么您应该努力进行单元测试。


这就是我在讨论时的感受。假设可以为一个用例编写完整而相关的集成测试,那么似乎单元测试将是一个有争议的话题。(尽管现在我写了这篇文章,但我正在考虑未来的意外情况-例如我们的后端已变成另一个应用程序的Web服务)。
杰夫·莱文

+1对于“您必须努力完成”的定义。说得好!
Andres F.

14

我不知道我接受你同事的论点。

  1. 如果注释掉了单元测试,那是因为有人在偷懒。那不是单元测试的错。而且,如果您开始以懒惰为借口,那么您几乎可以反对任何需要一点努力的做法。
  2. 如果操作正确,则单元测试不必花费很长时间。他们并没有阻止您从事更重要的工作。如果我们都同意修复损坏的代码比其他任何事情都重要,那么单元测试就非常重要。这是测试职位条件的简便方法。没有它,您怎么知道您已满足岗位条件保证?如果您还没有,那么您的代码将被破坏。

还有其他一些更令人信服的反对单元测试的理由。好吧,不完全反对他们。从DHH的测试引起的设计损害开始。他是一个有趣的作家,所以一定要读。我从中得到的是:

如果进行重大设计更改以适应单元测试,则它们可能会阻碍项目中的其他目标。如果可能,请问一个公正的人哪种方法更容易遵循。如果您难以理解的可高度测试的代码,则可能会失去从单元测试中获得的所有好处。过多抽象的认知开销会使您减速,并使泄漏错误变得更容易。不要打折。

如果您想获得细微差别和哲学性,这里有很多很棒的资源:


对该文章+1可能比我们在此处可以写的所有内容更好地回答OP案件
Doc Brown

懒惰是借口,第1点为+1。我无法承受如此令人沮丧的压力。我去过那儿。实际上,最近。
格雷格·伯格哈特

3
原因1并非单元测试的错误,但这仍然是反对单元测试的原因。
user253751

阅读DHH的文章后,请确保您观看了他与Kent Beck和Martin Fowler(他们在youtube上)讨论该话题的视频-很多人似乎比他的预期更为极端。在这些讨论中,他做了很多改进。
2015年

6

当发生重大代码更改时(新的POJO集,主要应用程序重构等),单元测试往往被注释掉而不是重新编写。

不要那样做 如果您找到这样做的人,请杀死他们并确保他们不会繁殖,因为他们的孩子可能会继承该有缺陷的基因。我从观看CSI电视节目中看到的关键是,在各处散布许多误导性的DNA样本。这样,您会以极大的噪音污染犯罪现场,鉴于DNA测试昂贵且耗时的特性,他们将其固定在您身上的可能性极低。


4

单元测试往往被注释掉而不是重新编写。

此时,代码审查过程会显示“修复单元测试”,这不再是问题:-)如果您的代码审查过程未发现所提交代码中的此类主要缺陷,那就是问题所在。


3

当发生重大代码更改时(新的POJO集,主要应用程序重构等),单元测试往往被注释掉而不是重新编写。

我总是尝试将重构和功能更改分开。当我需要同时执行这两项操作时,通常会先提交重构。

当重构代码而不更改功能时,现有的单元测试应该有助于确保重构不会意外破坏功能。因此,对于这样的提交,我将考虑禁用或删除单元测试是主要的警告信号。在检查代码时,应告知任何这样做的开发人员不要这样做。

不更改功能的更改仍可能由于有缺陷的单元测试而导致单元测试失败。如果您了解要更改的代码,则通常会立即发现此类单元测试失败的原因,并且易于修复。

例如,如果一个函数接受三个参数,则覆盖该函数的前两个参数之间的交互的单元测试可能没有注意为第三个参数提供有效值。重构测试后的代码可能会暴露出单元测试中的此缺陷,但是如果您了解代码应该执行的功能以及单元测试正在测试的内容,则很容易修复。

更改现有功能时,通常还需要更改某些单元测试。在这种情况下,单元测试有助于确保您的代码按预期更改功能,并且不会产生意外的副作用。

修复错误或添加新功能时,通常需要添加更多的单元测试。对于那些人,先提交单元测试然后再提交错误修复或新功能会很有帮助。这样可以更轻松地验证新的单元测试没有通过较旧的代码通过,而是通过了较新的代码通过。但是,这种方法并非完全没有缺陷,因此也存在支持同时提交新的单元测试和代码更新的争论。

最好将时间花在涉及用例的集成测试上,这会使范围较小的测试变得不那么重要。

这有一定道理。如果通过针对软件堆栈较高层的测试可以覆盖软件堆栈的较低层,则在重构代码时,测试可能会更有帮助。

我认为您不会在单元测试和集成测试之间的确切区别上达成共识。而且,如果您有一个测试用例,一个开发人员将其称为单元测试,而另一个称为集成测试,则无需担心,只要他们可以同意这是一个有用的测试用例即可。


3

进行集成测试以检查系统是否按照预期的方式运行,这始终具有业务价值。单元测试并非总是如此。例如,单元测试可以测试无效代码。

有一种测试哲学说,任何不给您信息的测试都是浪费。当不处理一段代码时,它的单元测试总是(或几乎总是)变成绿色,给您的信息很少或没有。

每种测试方法都不完整。即使您通过某种指标获得了100%的覆盖率,您仍然不会遍历几乎所有可能的输入数据集。因此,不要认为单元测试是魔术。

我经常看到有人声称进行单元测试可以改善您的代码。在我看来,单元测试给代码带来的改进是迫使不习惯它的人们将代码分解成小巧的,结构合理的片段。如果您习惯于通常以小巧,结构合理的方式设计代码,则可能会发现您不再需要单元测试来强迫您这样做。

根据单元测试的强度和程序的规模,最终可能会导致大量的单元测试。如果是这样,大的改变就变得更加困难。如果较大的更改导致大量的单元测试失败,则可以拒绝进行更改,进行大量的工作来修复大量的测试或放弃测试。没有一个选择是理想的。

单元测试是工具箱中的工具。他们在那里为您服务,而不是相反。确保从其中取出至少与放入的一样多的东西。


3

单元测试绝对不是某些支持者声称的灵丹妙药。但总的来说,它们的成本/收益比非常高。它们不会使您的代码“更好”,甚至不会更加模块化。“更好”具有比“可测试性”所暗示的更多的因素。但是他们将确保它在您考虑的所有情况和用法中都有效,并且将消除您的大多数错误(可能是更琐碎的错误)。

单元测试的最大好处是,它们使您的代码更灵活,更灵活地进行更改。您可以重构或添加功能,并确信自己没有破坏任何东西。仅此一项就使它们对大多数项目都值得。

提及您的朋友的观点:

  1. 如果添加POJO破坏了单元测试,则它们可能写得很差。POJO通常是您用来谈论域的语言,解决的问题很明显,更改它们意味着您的整个业务逻辑,而且表示代码可能必须更改。您不能指望什么能确保程序的那些部分不会改变...

  2. 抱怨单元测试很糟糕,因为人们将它们注释掉了,就像抱怨安全带不好是因为人们不戴安全带。如果有人注释掉测试代码并破坏了某些内容,那么他​​就应该与老板进行非常严肃而令人不愉快的谈话。

  3. 集成测试远胜于单元测试。没有问题。尤其是在测试产品时。但是编写好的,全面的集成测试将很难给您带来与单元测试相同的价值,覆盖率和正确性保证。


2

我只能想到一个反对单元测试的论点,这很弱。

当您在以后的阶段重构或更改代码时,单元测试将显示它们的大部分价值。您会发现添加功能并确保没有损坏任何东西所需的时间大大减少。

但是:首先,在编写测试时要付出(小的)代价。

因此:如果一个项目足够短并且不需要持续的维护/更新,那么编写测试的成本可能会超过其收益。


+1表示要认真回答所提出的问题。
RubberDuck

2
单元测试的费用肯定不小。好的单元测试通常比其测试花费更多时间。在大多数情况下,收益超过成本。
AK_

1
仅从理论上讲-重构时,更新单元测试的成本也很高,因为它们几乎总是过于精细。如果您正在谈论拥有一个好的集成测试套件,那么您将是对的。
gbjbaanb

如果一个项目足够短并且不需要持续的维护/更新 -大多数旧系统都是从没有测试的简短项目开始的-最终雇用了更多开发人员来维护增长的系统而没有测试
Fabio
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.