单元测试反模式目录

反模式 : 必须至少存在两个关键元素,才能正式地将实际的反模式与简单的坏习惯、坏实践或坏想法区分开来:

  • 一些重复的行为模式、过程或结构,最初看起来是有益的,但最终产生的坏结果多于有益的结果
  • 一个重构的解决方案,有清晰的文档记录,在实际操作中被证明是可重复的。

投票支持 TDD 反模式,您已经在“野外”看到过太多次了。
James Carr 的博客文章相关讨论: testdrivenddevelopment yahoogroup

如果你发现了一个“未命名”的. . 发布他们太。 每个反模式请一个职位使得投票有意义的东西。

我的既得利益是找到顶部 n 子集,这样我就可以在不久的将来的午餐会上讨论它们。

39858 次浏览
< p > 的嘲弄 < br > 有时候嘲讽是件好事,而且很方便。但有时开发人员可能会迷失自我,在他们努力模拟没有测试的东西的过程中。在这种情况下,一个单元测试包含了如此多的模拟、存根和/或伪造,以至于被测试的系统根本就没有被测试,相反,从模拟返回的数据就是被测试的内容

来源:James Carr的帖子。

< p > 检查员 < br > 为了达到100%的代码覆盖率而违反封装的单元测试,但是它对对象中正在发生的事情了解甚多,以至于任何重构的尝试都将破坏现有的测试,并要求在单元测试中反映任何更改

“我如何测试我的成员变量而不使它们为公共…”只是用于单元测试?'

巨大的

一个单元测试,尽管它是有效地测试被测对象,但它可以跨越数千行,并包含许多许多测试用例。这可以是被测试系统是上帝的对象的指示符(James Carr的帖子)。

对于这种情况,一个明确的迹象是测试跨越了不止几行代码。通常,测试是如此复杂,以至于它开始包含自己的错误或不可靠的行为。

慢戳

运行极其缓慢的单元测试。当开发人员开始测试时,他们有时间去洗手间,抽根烟,或者更糟的是,在一天结束回家之前开始测试。(Src: 詹姆斯·卡尔的帖子)

也就是那些不会像它们应该的那样频繁地运行的测试

本地英雄

一个测试用例,它依赖于编写它的开发环境中特定的东西来运行。结果是测试通过了开发箱,但是当有人试图在其他地方运行它时失败了。

隐藏的依赖性

与本地英雄密切相关的单元测试,需要在测试运行之前填充一些现有数据。如果这些数据没有被填充,测试就会失败,并且几乎没有给开发人员留下它想要什么或为什么的提示……迫使他们挖掘大量的代码来找出它所使用的数据应该来自哪里。


令人遗憾的是,我们已经多次看到古老的.dll依赖于模糊而多样的.ini文件,这些文件在任何给定的生产系统上都是不同步的,更不用说在没有与负责这些dll的三个开发人员广泛协商的情况下在您的机器上存在。叹息。

过度的设置——James Carr
一个甚至开始测试都需要巨大设置的测试。有时要用几百行代码来为一个测试准备环境,涉及到几个对象,由于所有设置的“噪音”,这可能很难真正确定测试的是什么。(Src: 詹姆斯·卡尔的帖子) < / p >

以铁链锁住一群做苦工的囚犯

必须以特定顺序运行的几个测试,即一个测试改变了系统的全局状态(全局变量,数据库中的数据),而下一个测试依赖于它。

您经常在数据库测试中看到这种情况。测试不是在teardown()中进行回滚,而是将更改提交给数据库。另一个常见的原因是对全局状态的更改没有包装在try/finally块中,如果测试失败,这些块将被清理。

肛门探测器

一个必须使用疯狂的、非法的或其他不健康的方式来执行其任务的测试,例如:使用Java的种setAccessible(真正的)读取私有字段或扩展一个类来访问受保护的字段/方法,或必须将测试放在某个包中以访问包的全局字段/方法。

如果您看到这种模式,则说明测试中的类使用了过多的数据隐藏。

这与Inspector之间的区别在于,被测类甚至试图隐藏需要测试的内容。因此,您的目标不是实现100%的测试覆盖率,而是能够测试任何东西。想象一个只有私有字段的类,一个没有参数的run()方法,根本没有getter。在不违反规则的情况下,没有办法进行测试。


Michael Borgwardt评论:这并不是一个真正的测试反模式,它是处理被测试代码中的缺陷的实用主义。当然,最好是修复这些缺陷,但在第三方库的情况下,这可能是不可能的。

Aaron Digulla:我有点同意。也许这个条目真的更适合“JUnit HOWTO”wiki,而不是反模式。评论?

面貌极相似的人

为了测试某些东西,您必须将测试中的部分代码复制到具有相同名称和包的新类中,并且必须使用类路径魔法或自定义类加载器来确保它首先是可见的(这样您的副本就会被拾取)。

此模式表明您无法从测试中控制隐藏依赖项的不健康数量。

我看着他的脸……我的脸!它就像一面镜子,但让我的血液凝固。

蝴蝶

您必须测试一些包含随时变化的数据的东西,比如包含当前日期的结构,并且没有办法将结果固定为一个固定的值。糟糕的是,您根本不关心这个值。它只会使您的测试更加复杂,而不会增加任何价值。

它翅膀上的蝙蝠可以在世界的另一端引发飓风。——爱德华·洛伦兹,蝴蝶效应

共用夹具不当——Tim Ottinger
测试夹具中的几个测试用例甚至不使用或不需要设置/拆卸。部分原因是开发人员惰性地创建了一个新的测试夹具…向

堆中添加一个测试用例更简单

搭便车/搭便车——James Carr, Tim Ottinger
与其编写一个新的测试用例方法来测试另一个截然不同的特性/功能,不如在现有的测试用例中使用一个新的断言(及其相应的操作,即从AAA开始的Act步骤)

布谷鸟——Frank Carver
一个单元测试,它与其他几个测试用例一起位于一个测试用例中,并享受与测试用例中的其他测试相同的(可能很长的)设置过程,但随后会从设置中丢弃部分或全部工件并创建自己的 高级症状:共用夹具不当

母鸡妈妈——Frank Carver
一个通用的设置,它所做的远远超过实际测试用例所需要的。例如,创建各种复杂的数据结构,填充明显重要和唯一的值,而测试只断言存在或不存在某些东西 高级症状:共用夹具不当

我不知道它能做什么…以防万一,我还是加进去了。—匿名开发人员

愉快路径

测试保持在满意的路径上(即预期的结果),而不测试边界和异常。

JUnit反模式

秘密捕手——Frank Carver
由于缺少断言,乍一看似乎没有进行任何测试的测试。但是“细节决定成败”。测试实际上依赖于要抛出的异常,并期望测试框架捕获异常并将其作为失败报告给用户。< / p >

[Test]
public void ShouldNotThrow()
{
DoSomethingThatShouldNotThrowAnException();
}

沉默的捕手——Kelly?< br > 如果抛出异常则通过的测试。即使实际发生的异常与开发人员预期的异常不同 参见:秘密的捕手

[Test]
[ExpectedException(typeof(Exception))]
public void ItShouldThrowDivideByZeroException()
{
// some code that throws another exception yet passes the test
}

二等公民 -测试代码不像生产代码那样重构,包含大量重复的代码,使得维护测试变得困难。

没有名字的测试—尼克佩洛

添加到错误跟踪器中以重现特定错误的测试,并且其作者认为不需要有自己的名称。不是增强现有的、缺乏的测试,而是创建一个名为testForBUG123的新测试。

两年后,当测试失败时,您可能需要首先尝试在错误跟踪器中找到bug -123,以弄清楚测试的意图。

40英尺杆子测试

由于害怕与要测试的类过于接近,这些测试与要测试的逻辑隔着无数抽象层和数千行代码。因此,它们非常脆弱,容易受到各种副作用的影响,这些副作用发生在往返感兴趣的班级的史诗般的旅程中。

图灵测试

由一些昂贵的工具自动生成的测试用例,该工具使用一些过于聪明的数据流分析,从被测类中收集了许多断言。让开发人员产生一种错误的信心,认为他们的代码经过了良好的测试,使他们免于设计和维护高质量测试的责任。如果机器可以为你编写测试,为什么它不能抽出手指来自己编写应用程序呢!

你好笨。——世界上最聪明的电脑给新学徒(来自旧的Amiga漫画)。

环境破坏者

一个用于各种“需求”的“单元”测试开始溢出到其环境中,使用和设置环境变量/端口。同时运行两个这样的测试会导致“端口不可用”异常等。

这些测试将是断断续续的,开发人员会说“再运行一次”之类的话。

我看到的一个解决方案是随机选择一个端口号来使用。这降低了冲突的可能性,但显然不能解决问题。因此,如果可以,总是模拟代码,这样它就不会分配不可共享资源。

沉睡者,又名维苏威火山—尼克佩洛

注定在未来某个特定时间和日期失败的测试。这通常是由于在测试使用Date或Calendar对象的代码时不正确的边界检查造成的。有时,如果在一天中非常特定的时间(如午夜)运行测试可能会失败。

不要将“The Sleeper”与“等着瞧”反模式混淆。

该代码早在2000年之前就将被替换—1960年的许多开发者

等着瞧

一种运行一些设置代码的测试,然后需要“等待”一段特定的时间,才能“看到”被测代码是否按预期运行。使用Thread.sleep()或等效的testMethod肯定是一个“等待和观察”测试。

通常,如果测试正在测试生成系统外部事件(如电子邮件、http请求或将文件写入磁盘)的代码,您可能会看到这种情况。

这样的测试也可以是当地的英雄,因为当它在较慢的机器或过载的CI服务器上运行时,它将失败。

不要将Wait and See反模式与的卧铺混淆。

< p > 当我看到一些闪烁的gui时,我才会相信 < br > 一种不健康的固定/痴迷于通过其GUI测试应用程序“就像一个真正的用户”

通过GUI测试业务规则 是一种可怕的形式的耦合。如果 您编写了数千个测试 然后改变你的GUI, 成千上万的测试中断 相反,只通过GUI测试GUI内容,并将 图形用户界面以虚拟系统代替 真正的系统,当你做这些测试的时候。 通过API测试业务规则 不涉及GUI。——Bob Martin

__abc0——__abc1

闪烁测试(来源:Romilly Cocking)

一个测试只是偶尔失败,而不是在特定的时间,通常是由于测试中的竞争条件。通常在测试异步的东西时发生,比如JMS。

可能是'等着瞧'反模式和'的卧铺'反模式的超集。

构建失败了,那就再运行一次构建吧。—匿名开发人员

死树

一个创建了存根,但实际上没有编写测试的测试。

实际上,我在我们的产品代码中看到过这种情况:

class TD_SomeClass {
public void testAdd() {
assertEquals(1+1, 2);
}
}

我都不知道该怎么想了。

今天被这个咬了一口:

< p > 潮湿的地板上: < br > 测试创建的数据被持久化到某个地方,但是测试完成后并不清理。这将导致后续测试运行中的测试(相同的测试,或可能是其他测试)失败

在我们的例子中,测试在“temp”目录中留下了一个文件,该文件具有第一次运行测试的用户的权限。当不同的用户尝试在同一台机器上测试时:砰。在James Carr网站上的评论中,Joakim Ohlrogge将其称为“邋遢的工人”,这也是“慷慨的剩菜”的灵感来源之一。我更喜欢我的名字(不那么侮辱人,更熟悉)。

线打击

乍一看,测试覆盖了所有内容,代码覆盖率工具也100%地证实了这一点,但实际上测试只击中了代码,没有任何输出分析。

< a href = " https://stackoverflow.com/questions/10129864/coverage-vs-reachable-code " > coverage-vs-reachable-code < / >

测试一切

我不敢相信直到现在还没有提到这一点,但测试不应该破坏单一责任原则

我遇到过很多次这样的情况,破坏这个规则的测试从定义上来说是维护的噩梦。

连体双胞胎

人们称之为“单元测试”的测试实际上是集成测试,因为它们没有与依赖项(文件配置、数据库、服务,换句话说,其他没有在测试中测试的部分,人们懒惰而没有隔离)隔离开来,并且由于应该stub或mock的依赖项而失败。