单元测试反模式目录


203

反模式:必须至少存在两个关键要素,才能将实际的反模式与简单的坏习惯,坏习惯或坏主意正式区分开:

  • 某些重复的动作,过程或结构模式似乎最初是有益的,但最终会产生比有益结果更多的不良后果,并且
  • 重构的解决方案已明确记录在案,并在实践中得到证明,并且可重复。

投票一次您在“野外”看到的TDD反模式太多了。
詹姆斯·卡尔(James Carr)的博客文章以及 有关测试驱动开发yahoogroup的相关讨论

如果您找到了一个“未命名”的人。每个反模式请发表一篇文章,以使投票数有所作为。

我的既得利益是找到前n个子集,以便我可以在不久的将来在午餐会上讨论'em'。


亚伦,您似乎遍地都是:)将标语或标语添加为注释,以便我们可以减少滚动时,是个好主意吗?
Gishu

1
这是相当不错的..谢谢大家n gals。让他们
来吧

2
+1喜欢这个主题!!!而且其中大多数都是如此,如此盛行!
CHII

不错的话题,为什么这个社区维基呢???
Quibblesome

2
因为这是一项民意调查-您不会想要收获代表,因为您发布的是最常见的反模式类型;)
Gishu

Answers:


70

二等公民 -测试代码的重构程度不如生产代码,因为其中包含大量重复的代码,因此难以维护测试。


67

搭便车/搭载-James Carr,Tim Ottinger
而不是编写新的测试用例方法来测试另一个/独特的功能/功能,而是在现有的测试用例中使用新的断言(及其相应的动作,即来自AAA的Act步骤) 。


15
是的,那是我最喜欢的那个。我一直都在做。哦...等等...你说这是一件坏事。:-)
guidoism 2010年

1
我不确定这是否是反模式。所有不变量必须true在每个可能的mutator调用之后。因此,您将要检查每个不变式都true在要测试的变体和输入数据的每种组合之后。但是,您将希望减少重复,并确保检查所有不变量,包括当前不会导致测试失败的不变量。因此,您将它们全部放在checkInvariants()验证功能中,并在每次测试中都使用它。代码更改,并添加了另一个不变式。当然,您也将其放在函数中。但这是一个搭便车的人。
Raedwald

2
@Raedwald-随着时间的流逝,测试名称不再与它测试的所有内容匹配。另外,由于相互交织的测试,你也会有些挣扎; 故障并不能指出故障的确切原因。例如,该测试的一个典型示例将读取诸如所有排列步骤的不透明超集之类的>>行动>>断言A >>行动更多>>断言B >>行动更多>>断言C。现在理想情况下,如果A和C为损坏,您应该看到2个测试失败。通过上面的测试,您只会看到一个,然后修复A,在下一次运行时,它会告诉您现在C已损坏。现在想象5-6不同的测试融合在一起..
Gishu

1
“测试名称不再与它测试的所有内容匹配”仅当测试是针对最初存在的发布条件命名的。如果您使用方法名称,设置状态和输入数据(方法参数)的组合进行命名,则没有问题。
Raedwald

“失败并不指出失败的确切原因”没有断言失败可以指示失败的原因。这需要深入研究实现细节:调试回归失败,了解一些TDD工作的开发状态。
Raedwald

64

快乐之路

该测试停留在令人满意的路径(即预期结果)上,无需测试边界和异常。

JUnit反模式


原因:时间限制过大或懒惰。重构解决方案:花一些时间编写更多测试以消除误报。后一个原因需要鞭打。:)
Spoike

59

本地英雄

一个测试用例,它依赖于特定的开发环境才能运行。结果是测试通过了开发箱,但在有人尝试在其他地方运行时失败。

隐藏的依赖

与本地英雄密切相关的单元测试,需要在测试运行之前在某处填充一些现有数据。如果没有填充该数据,则测试将失败,并且几乎没有向开发人员表明它想要什么,或者为什么……迫使他们挖掘大量的代码以查明其使用的数据应该来自哪里。


令人遗憾的是,对于依赖于模糊和变化的.ini文件的古老.dll,它们在任何给定的生产系统上总是不同步,这已经太多次了,更不用说在没有与负责这些dll的三位开发人员进行广泛咨询的情况下在您的计算机上存在了。叹。


这是WOMPC开发人员首字母缩略词的一个很好的例子。“在我的电脑上工作!” (通常说来让测试人员摆脱
困境

58

链帮

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

您经常在数据库测试中看到这一点。测试无需在中进行回滚,而是teardown()将其更改提交到数据库。另一个常见的原因是,对全局状态的更改没有包装在try / finally块中,这些块在测试失败时会清除。


这只是简单的讨厌。中断测试必须是独立的概念。但我已经在多个地方读到它..猜“流行TDD”是相当搞砸了
Gishu

56

嘲弄
有时嘲讽可以很好,和方便。但是有时开发人员可能会迷失自我,无法模拟出未经测试的内容。在这种情况下,单元测试包含许多模拟,存根和/或伪造品,以至于根本没有对被测系统进行测试,而是从模拟返回的数据正在被测试。

资料来源:詹姆斯·卡尔(James Carr)的帖子。


2
我相信这是因为您所测试的类有太多的依赖关系。重构的替代方法是提取可以隔离的代码。
Spoike

@Spoike; 如果您使用的是真正依赖于类角色的分层体系结构;一些层往往比其他层具有更多的依赖性。
krosenvold

最近,我在一个受人尊敬的博客中看到了要从模拟存储库返回的模拟实体设置的创建。WTF?为什么不首先实例化一个真实的实体。就我自己而言,我只是被一个模拟接口所烦扰,在该接口中,我的实现四处抛出NotImplementedExceptions。
Thomas Eyde

40

沉默的守望者 -凯利?
如果引发异常,则通过的测试..即使实际发生的异常与开发人员预期的异常不同。
另请参阅:秘密捕手

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

那是一个棘手且危险的操作(即,使您认为您测试的代码每次运行时都会爆炸)。这就是为什么我尝试同时详细说明异常类和消息中的某些独特内容。
2015年

34

Inspector
单元测试,它违反封装以达到100%的代码覆盖率,但是对对象中发生的事情非常了解,因此任何重构的尝试都会破坏现有测试,并要求任何更改都应在单元中反映出来测试。


“如何在不公开成员变量的情况下测试我的成员变量…… 用于单元测试?”


2
原因:荒谬地依赖白盒测试。有一些工具可以生成此类测试,例如.NET上的Pex。重构解决方案:代替测试行为,如果您真的需要检查边界值,那么让自动化工具生成其余部分。
Spoike

1
在Moq出现之前,我不得不放弃模拟框架,而要手写我的模拟。将测试与实际实现联系起来太容易了,几乎不可能进行任何重构。我无法分辨出区别,除了Moq,我很少犯这类错误。
Thomas Eyde

34

设置过多 -詹姆斯·卡尔(James Carr)
一种测试,需要进行大量设置才能开始测试。有时,使用数百行代码为一个测试准备环境,其中涉及多个对象,由于所有设置的“噪音”,使得很难真正确定要测试的内容。(来源:詹姆斯·卡尔的帖子


我知道过多的测试设置通常指向a)结构不良的代码或b)模拟不足,对吗?
Topher Hunt 2014年

嗯,每种情况都可能不同。这可能是由于高耦合。但是通常这是一种过度规范的情况,它指定(模拟期望)场景中的每个协作者-这将测试与实现耦合在一起,并使它们变得脆弱。如果对协作者的呼叫是测试的附带细节,则不应将其包含在测试中。这也有助于使测试简短易读。
Gishu 2014年

32

肛门探针

必须使用疯狂,非法或其他不健康的方式执行测试的测试,例如:使用Java的setAccessible(true)读取私有字段或扩展类以访问受保护的字段/方法,或者必须将测试放入特定的程序包中以进行访问打包全局字段/方法。

如果看到此模式,则被测类将使用过多的数据隐藏。

此代码与The Inspector之间的区别在于,被测类试图甚至隐藏您需要测试的内容。因此,您的目标不是获得100%的测试覆盖率,而是能够测试所有内容。想一想一个只有私有字段,run()没有参数且根本没有getter 的方法的类。没有违反规则的方法就无法进行测试。


Michael Borgwardt的评论:这实际上不是一个测试反模式,它是一种实用主义,可以解决所测试代码中的缺陷。当然,最好修复这些缺陷,但是对于第三方库来说,这可能是不可能的。

亚伦·迪古拉(Aaron Digulla):我有点同意。也许此条目确实更适合“ JUnit HOWTO” Wiki,而不适合反模式。注释?


这和检查员不一样吗?
Gishu

1
嗯..这行“被测类试图隐藏甚至需要测试的东西”表明该类和测验之间存在权力斗争。如果应该进行测试..应该可以通过某种方式公开访问..通过类的行为/接口..这种方式有点违反封装的味道
Gishu

2
npellow:Maven2有一个插件,不是吗?
亚伦·迪古拉

1
这并不是真正的测试反模式,它是实用的,可以解决所测试代码中的缺陷。当然,最好修复这些缺陷,但是对于第三方库来说,这可能是不可能的。
Michael Borgwardt 2009年

1
IDK,它必须具有某种副作用。我会测试副作用。不确定您对测试第三方API的含义,我认为您应该将其包装在可以测试是否正确使用的自己的代码中,然后针对第三方API对该代码进行集成测试。无法对第三方代码进行单元测试。
2015年

26

没有名字的测试 -尼克·佩洛

为在错误跟踪器中重现特定错误而添加的测试,其作者认为自己的名字不做保证。除了增强现有的缺少的测试外,还创建了一个名为testForBUG123的新测试。

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


7
如此真实。这比称为“ TestMethod”的测试更有帮助
NikolaiDante

8
除非错误追踪变化,你失去的旧跟踪器和其发行的标识符...所以PROJECT-123不再意味着什么....
CHII

25

慢戳

运行非常慢的单元测试。开发人员启动测试时,他们有时间去洗手间,抽烟,或者更糟糕的是,在一天结束之前回家进行测试。(来源:詹姆斯·卡尔的帖子

亦即不会频繁运行的测试


一些测试由于其性质而运行缓慢。如果您决定不像其他服务器那样频繁运行它们,请确保它们至少至少在CI服务器上运行一次。
克里斯·韦斯特

这是一个显而易见的问题,但是最普遍的解决方法是什么?
Topher Hunt 2014年

最初看起来是有益的,是吗?
凯夫2014年

1
@TopherHunt通常,测试速度较慢,因为它们具有某些昂贵的依赖项(即文件系统,数据库)。诀窍是分析依赖性,直到发现问题为止,然后将依赖性推向调用堆栈。我写了一个案例研究,通过修正学生的依存关系,使学生的单元测试套件从77秒提高到0.01秒:github.com/JoshCheek/fast_tests
Joshua Cheek

20

蝴蝶

您必须测试某些包含随时更改的数据的东西,例如包含当前日期的结构,并且无法将结果固定为固定值。丑陋的部分是您根本不在乎此值。它只会使您的测试变得更加复杂,而不会增加任何价值。

机翼上的蝙蝠会在世界的另一端造成飓风。-爱德华·洛伦兹(Edward Lorenz),《蝴蝶效应》


这里的反模式是什么:像这样的测试是什么样的?有解决办法吗?System.DateTime.Now除了具有更简单或更确定性的单元测试之外,被测代码是否有消除诸如的依赖之类的可争议优势?
2013年

1
在Java中,一个示例是调用toString()不会覆盖该方法的对象。这将为您提供取决于内存地址的对象的ID。或toString()包含对象的主键,并且每次运行测试时都会更改。可以通过以下三种方法来解决此问题:1.更改要测试的代码,2.使用regexp删除测试结果的可变部分,或3.使用功能强大的工具覆盖系统服务以使它们返回可预测的结果。
亚伦·迪古拉

造成这种反模式的根本原因是,被测试的代码并不关心测试它的工作量。因此,开发人员的想法是蝴蝶的翅膀,这会在其他地方引起问题。
亚伦·迪古拉

19

闪烁测试(来源:Romilly Cocking)

测试偶尔会失败,而不是在特定时间失败,并且通常是由于测试中的比赛条件所致。通常在测试异步内容(例如JMS)时发生。

可能是“ 等待并查看 ”反模式和“ 睡眠者 ”反模式的超集。

构建失败,哦,好了,再次运行构建。-匿名开发人员


@Stuart-必须观看的视频,描述这是“汽车停转-立即尝试!” videosift.com/video/… 这种模式也可以称为“立即尝试!”,也可以称为“ The Flakey Test”
npellow,

1
我曾经为PRGN编写过一个测试,以确保正确分发。有时,它会随机失败。去搞清楚。:-)
克里斯·韦斯特

1
这不是一个好的测试吗?如果测试失败,则需要跟踪问题的根源。我和某人打了一场考试,考试在9p和午夜之间失败了。他说这是随机的/断断续续的。最终将其追溯到与时区有关的错误。去搞清楚。
特伦顿

@克里斯蒂安·马斯特·汉森(Christian Vest Hansen):你不能播种吗?
安德鲁·格林

@trenton如果开发人员可以费心去追踪它,而不是仅仅忽略它(它们在大多数时候都可以摆脱它),这是一个很好的测试。
谢泼德

19

等着瞧

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

通常,如果测试是测试代码,该代码会在系统外部生成事件,例如电子邮件,http请求或将文件写入磁盘,则可能会看到此消息。

这样的测试也可能是Local Hero,因为它在较慢的设备箱或CI服务器过载时运行失败。

不要将Wait and See反模式与The Sleeper混淆。


嗯..嗯,我用这样的东西。我还能如何测试多线程代码?
Gishu

@Gishu,您真的要对同时运行的多个线程进行单元测试吗?我尝试仅对run()方法独立执行的操作进行单元测试。一种简单的方法是调用run()-它将阻塞,而不是单元测试中的start()。
npellow

@Gishu使用CountDownLatches,Semaphores,Conditions等,使线程彼此告诉它们何时可以前进到下一个级别。
克里斯·韦斯特

例如:madcoderspeak.blogspot.com/2008/11/…Brew按钮evt。观察者每隔一段时间轮询一次,并引发更改的事件。在这种情况下,我添加了一个延迟,以便轮询线程有机会在测试退出之前运行。
纪州

我认为卡通链接已损坏。
安德鲁·格林

17

夹具共享不当 -Tim Ottinger
测试夹具中的几个测试用例甚至不使用或不需要安装/拆卸。部分是由于开发人员的惯性来创建新的测试夹具...更容易在堆中添加另一个测试用例


1
也可能是受测类正在尝试做太多事情。
迈克2

16

巨人

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

对此的肯定的标志是跨越多行代码的测试。通常,测试是如此复杂,以至于开始包含其自身或片状行为的错误。


15

当我看到一些闪烁的GUI时,我会相信。
通过其GUI“像真实用户一样”测试应用程序,这是不健康的尝试/痴迷。

通过GUI测试业务规则是一种可怕的耦合形式。如果您通过GUI编写了成千上万的测试,然后更改了GUI,那么成千上万的测试就会失败。
相反,在运行这些测试时,仅通过GUI测试GUI事物,并将GUI耦合至虚拟系统而不是真实系统。通过不涉及GUI的API测试业务规则。-鲍勃·马丁

“您必须了解眼见为实,但也要知道眼见为实。” - 丹尼斯·威特利


1
如果您认为刷新GUI是错误的,我看到有人写了一个jUnit测试来启动GUI,并且需要用户交互才能继续。它挂了其余的测试套件。测试自动化就这么多!
Spoike

我不同意。测试GUI很困难,但是它们也是错误的根源。不测试它们只是懒惰。

3
这里的要点是,您不应该测试GUI,而不仅仅是通过GUI进行测试。您可以在没有GUI的情况下执行“无头”测试。保持GUI尽可能薄-使用MVP风格-然后您可以完全不用测试它。如果您发现始终在薄的GUI层中出现错误,请使用测试进行覆盖。但是大多数时候,我认为这样做不值得。GUI“接线”错误通常更容易解决...
Gishu

@Spoike:指导性的手动测试也不错,使用jUnit(或任何其他单元测试框架)来驱动不是单元测试的自动化测试也不错。您只是不应该将它们放在同一个项目中,也不应该像对待单元测试一样对待它们(例如,持续运行或在每次构建后运行)。
2013年

1
@ MerlynMorgan-Graham我同意,我并不是说您不应该测试GUI。团队成员坚信可以将指导性手动测试与自动测试混合使用,这让我感到不安。后来我发现,这是让不习惯TDD的所有人停止使用它的绝佳方法。我发现,如果要遵循TDD流程,则将功能测试(易失性)与单元测试(应该是稳定的)混合在一起是不好的。
Spoike

14

卧铺者,又名维苏威火山 -尼克·佩洛

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

请勿将“卧铺者”与“ 等待并看到 ”反模式混淆。

该代码将在2000之前被替换 -1960年有许多开发人员


我宁愿称其为休眠的火山:) ..但是我知道您在说什么..例如,在撰写本文时,作为测试的未来日期选择的日期将成为该日期的当前/过去日期。过去了..打破测试。您能发表一个例子..只是为了说明这一点。
Gishu

@Gishu-+1。我当时的想法是一样的,但无法在两者之间做出决定。我更新了标题以使其更加清楚;)
npellow

11

死树

创建存根但未实际编写测试的测试。

我实际上已经在生产代码中看到了这一点:

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

我什至不知道该怎么想。


8
:)-也称为流程合规性后门。
Gishu

1
最近,我们在反复重构的测试和被测方法中有了一个示例。经过几次迭代后,测试成为对被测方法的调用。而且由于该方法现在返回了void,因此没有任何要断言的断言。因此,基本上,测试只是确保该方法没有引发异常。实际是否有用或正确都没关系。我在代码审查中找到了它,然后问:“那么……我们在这里还要测试什么?”
Marvo 2015年

11

今天被这个咬了:

湿地板
测试创建的数据会保留在某处,但测试完成后不会清除。这会导致测试(同一测试或可能其他测试)在后续测试运行中失败。

在我们的例子中,测试在“ temp”目录中保留了一个文件,该文件具有首次运行该测试的用户的权限。当其他用户尝试在同一台机器上进行测试时:繁荣。在詹姆士卡尔(James Carr)网站上的评论中,约阿金(Joakim Ohlrogge)将其称为“草率工人”,这是“慷慨剩菜”灵感的一部分。我更喜欢我的名字(侮辱更少,更熟悉)。


您可以使用junit的临时文件夹规则来避免地板潮湿。
DaveFar 2011年

这种类型与持续集成反模式有关。在CI中,每个开发人员都应该拥有自己的工作空间和资源,并且构建机器也应该是其自己的环境。然后,您可以避免权限问题之类的事情(或者可能最终将它们隐藏起来,以使它们仅在生产中出现。)
Marvo 2015年

11

杜鹃 -弗兰克·卡弗(Cuckoo -Frank Carver)
单元测试,它与其他多个测试案例一起位于一个测试案例中,并且与该测试案例中的其他测试具有相同的(可能很长)设置过程,但随后会丢弃设置中的部分或全部工件并创造自己的。
的高级症状:不当共享治具


10

秘密捕手 -弗兰克·卡弗(Frank Carver)
一种测试,由于没有断言,乍看之下似乎没有进行任何测试。但是“魔鬼在细节中” ..测试实际上依赖于抛出异常,并且期望测试框架捕获异常并将其报告给用户为失败。

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

2
在我看来,这实际上是一个有效的测试-尤其是作为回归测试。
IljaPreuß08年

再次遗憾的是,它与Silent catcher混淆了...单元测试应该清楚地说明要测试的内容,而不是说“应该工作”。(+ 1 tp总比没有好。特别是如果您处于旧式回归中国家/地区)
Gishu

1
在这类测试中,我至少要捕获Exception并将其分配给变量。然后我断言不为null。
Thomas Eyde

4
一些框架具有Assert.DoesNotThrow(SomeDelegateType act)样式声明,可以在这种情况下专门使用。我发现这比在构造函数返回非null时成功而在构造函数抛出时失败的测试用例要少。构造函数永远不会返回null。(注意:仅适用于保证构造函数返回非null的语言)
Merlyn Morgan-Graham

10

环境破坏者

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

这些测试将是间歇性的,并使开发人员说诸如“再次运行它”之类的事情。

我见过的一种解决方案是随机选择要使用的端口号。这样可以减少发生冲突的可能性,但是显然不能解决问题。因此,如果可以的话,请始终对代码进行模拟,以使其实际上不会分配不可共享的资源。


@gcrain ..测试应该是确定性的。IMO更好的方法是在测试之前和之后正确使用“团队中知名的”端口进行测试和清理,以使其始终可用...
Gishu

1
@gishu-问题不在于没有setup()和teardown()方法来处理这些端口。问题是,例如,运行CI服务器,并同时运行多个版本的测试,尝试使用相同的,经过测试的硬编码端口号
gcrain

10

图灵测试

一个测试用例是由一些昂贵的工具自动生成的,该工具使用一些过于精明的数据流分析方法,从被测类中收集了许多很多断言。使开发人员误以为他们的代码已经过良好的测试,这使他们免于设计和维护高质量测试的责任。如果机器可以为您编写测试,为什么不能伸出手指来编写应用程序呢!

你好笨 -世界上最聪明的计算机,到新的学徒(从旧的Amiga漫画)。


10

四十英尺杆测试

由于这些测试过于接近他们要测试的类,因此它们之间的距离很远,被无数的抽象层和他们正在检查的逻辑中的数千行代码分隔开。因此,它们极易碎,并且容易受到往返于感兴趣类的史诗般旅程中发生的各种副作用的影响。


9

多佩尔甘格

为了测试某些内容,您必须将被测代码的一部分复制到具有相同名称和程序包的新类中,并且必须使用classpath magic或自定义类加载器来确保首先可见(因此,请选择副本)向上)。

此模式表示您无法通过测试控制的隐藏依赖项数量不正常。

我看着他的脸...我的脸!它就像一面镜子,但使我的血液冻结了。


7

母鸡 -弗兰克·卡弗(Frank Carver)
这是一种通用设置,其功能远远超出实际测试用例的需求。例如,当测试仅断言某些事物的存在或不存在时,创建各种复杂的数据结构,这些结构中填充了看似重要的唯一值。
的高级症状:夹具共享不当

我不知道它的作用...我还是在添加它,以防万一。-匿名开发人员


7

全部测试

我不敢相信到目前为止还没有提到这一点,但是测试不应该违反“ 单一责任原则”

我已经碰到了很多次,从定义上说,违反该规则的测试是一场噩梦。


6

打线者

乍看之下,测试涵盖了所有内容,并且代码覆盖率工具可以100%确认这一点,但实际上,测试仅会击中代码而没有任何输出分析。

覆盖率-可达代码

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.