单元测试编码标准


22

通常,在谈论编码标准时,我们指的是程序本身的代码,但是单元测试呢?是否存在某些单元测试独有的某些编码标准准则?这些是什么?

Answers:


12

我不禁想到了测试代码的三种编码风格差异。

在命名测试方法时,我遵循的模式shouldDoSomethingWhenSomeConditionHolds

在测试中,通常遵循以下间距模式:

@Test
shouldReturnAccountBalenceWhenGetBalenceIsCalled() {
    // Some lines 
    // of setup code
    // go here.

    // The action being tested happens after a blank line.

    // An assertion follows another blank line.
}

有些人坚持每个测试仅一个断言,但这远非普遍。

测试代码中的DRY(不要重复自己)比生产代码中的考虑要少。虽然应该将某些重复的代码放置在setUp方法或t​​estUtils类中,但是在测试代码中争取零重复将导致紧密耦合且不灵活的测试,从而不利于重构。


当然,有多种模式,这就是为什么您也应该提供答案的原因。
埃里克·威尔逊

10
那就是Arrange,Act,Assert模式。
StuperUser 2011年

干仍然很重要。如果您需要在多个测试中执行相同的断言,请创建通用函数并在所有测试中调用它。
MiFreidgeim停止恶行,2017年

@MichaelFreidgeim也许我们只是在谈论度数,但是我对测试代码的重复性有更高的容忍度。我有几次构建测试套件的经验,很少重复,并且发现当需求改变时,测试变得难以修改和理解。然后,我不再担心测试中的DRY,我的测试套件更易于使用。<耸肩>
埃里克·威尔逊

16

Roy Osherove建议使用以下模式来命名测试:

NameOfMethodUnderTest_StateUnderTest_ExpectedBehavior() 

参见http://weblogs.asp.net/rosherove/archive/2005/04/03/TestNamingStandards.aspx


我同意罗伊。尽管ReSharper一直告诉我我应该将其删除NameOfMethodUnderTestStateUnderTestExpectedBehavior();但它可以提高可读性;)
Oscar Mederos

方法重载时如何使这项工作有效,因此可以有多个同名方法?
Narendra Pathai 2014年

6

最主要的是要记住单元测试本质上是微型规范。这意味着重点必须始终放在可读性上。

首先,这意味着名称必须明确传达正在测试的内容和主张的内容。

其次,尽管有时被遗忘的是,作为规范,它们应该这样做-指定行为。也就是说,单元测试不应包含逻辑-否则它们有可能陷入重复程序功能而不是对其进行测试的陷阱。

有时测试涉及的对象设置起来很复杂,您应该使用对象母体测试数据构建器之类的方法,努力使此设置逻辑与测试分开。

我将以一些书建议作为结尾:

xUnit测试模式:重构测试代码:很棒的书,有人说这有点干,但是我不这么认为。详细介绍了组织测试的多种不同方式以及如何使其保持可维护性。如果您使用的是类似NUnit之类的东西,则相关。

单元测试的技巧:.Net中的示例:有关编写和维护测试的精髓的最佳书籍。尽管真的很新,但我发现模拟部分已经过时了,因为AAA语法现在已经很标准了,而不仅仅是另一种实现方式。

在测试的指导下,不断发展的面向对象软件:这本书真是太神奇了!迄今为止,最好的单元测试书和唯一的高级手册将单元测试视为设计过程中的头等公民。在公开测试版中正在阅读此书,此后一直在推荐。整本书中都使用了出色的真实示例。但是建议您先阅读罗伊的书。


恕我直言,单元测试可以包含逻辑:完全合理的方法是,通过使用天真的算法来测试算法的高度优化,高效版本,该算法可以执行相同的操作来确定正确的行为。例如,假设通过构建基于线性搜索的关联数组来测试哈希表。
dsimcha

2
是的,但这属于测试数据构建器中测试之外的内容(如果它们内部的逻辑不是微不足道的,则应对其进行单元测试)。例外的是第三方库,通常“信任”第三方库是正确的,并且无需测试即可使用。
FinnNk 2010年

3

不要在单元测试中添加逻辑。例如,假设您正在测试一个add方法,则可能会有类似以下内容:

void MyTest_SaysHello()
{
   string name = "Bob";
   string expected = string.Format("Hello, {0}", name);
   IMyObjectType myObject = new MyObjectType();
   string actual = myObject.SayHello(name);
   Assert.AreEqual(expected, actual);
}

在这种情况下,您可能会重复与测试中相同的逻辑,因此实际上是在测试“ 1 + 1 == 1 + 1”,而不是“ 1 + 1 == 2”,即“真实”测试。因此,您真正想要的测试代码是:

void MyTest_SaysHello()
{
   string expected = "Hello, Bob";
   IMyObjectType myObject = new MyObjectType();
   string actual = myObject.SayHello("Bob");
   Assert.AreEqual(expected, actual);
}

2
小改正:我认为您的意思是'字符串期望= string.Format(“ Hello,Bob”)'应该是'字符串期望=“ Hello,Bob”'。
Mike Rosenblum

@MikeRosenblum您显然是对的,有人试图对其进行纠正,但有两位审稿人拒绝了此编辑
Konrad Morawski 2014年

@Konrad:奇怪。这是一个编程论坛,对吧?
Mike Rosenblum 2014年

我已按照Mike Rosenblum的建议再次编辑了答案。
bdsl

0

描述性的长方法名称。 请记住,永远不会从代码中调用测试方法(它们是通过单元反射运行程序通过反射来发现和调用它们的),因此,发疯并使用50-80个字符长的方法名称是可以的。特定的命名约定(驼峰式,下划线,“应该”,“必须”,“何时”,“给出”等)并不重要,只要名称能回答以下三个问题即可:

  • 正在测试什么?
  • 有什么条件?
  • 预期的结果是什么?

测试方法应简短

测试方法应具有简单的线性结构。没有if或loop构造。

测试方法应遵循“排列-作用-断言”模式

每个测试应测试一件事。这通常意味着每个测试一个声明。像这样的测试{ Do A; Assert B; Assert C; }应重构为两个:{ Do A; Assert B; }{ Do A; Assert C; }

避免使用随机数据或类似“ DateTime.Now”的内容

确保在测试结束时所有测试夹具成员都恢复到原始状态(例如,使用拆解

即使您无情地删除了生产代码中的重复项,测试夹具中的代码重复项也没有那么重要。


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.