编写单元测试的标准

11

我计划为我的团队引入一套编写单元测试的标准,但要包括哪些内容呢?

这两篇文章(单元测试命名最佳实践单元/集成测试文件系统依赖的最佳实践)已经启发了我一些想法。

应该包括其他领域的内容,例如如何设置测试类以及如何组织它们。例如,如果有一个名为OrderLineProcessor的类,则应有一个名为OrderLineProcessorTest的测试类。如果该类上有一个名为Process()的方法,则应该有一个名为ProcessTest的测试(也许需要多个测试来测试不同状态)。

还有其他需要包括的内容吗?

你的公司是否有单元测试的标准?

编辑:我使用Visual Studio Team System 2008并使用C#.Net开发。


@Gerrie刚刚回答了这个旧问题,看看吧。 - eglasius
10个回答

9
请查看Michael Feathers关于单元测试的定义(或者说是什么让单元测试成为了糟糕的单元测试)。
请了解“安排,操作,验证”的概念,即测试只做三件事情,并且顺序固定:
- 安排任何需要用到的输入数据和处理类 - 执行被测试的操作 - 使用一个或多个验证来测试结果。是的,可以使用多个验证,只要它们都能够测试执行的操作。
请参考行为驱动开发,以便将测试用例与需求对齐。
此外,我认为现在标准文档不必要每次都写,因为已经有很多可用的资源了。可以链接到这些资源,而不是重复它们的内容。为想要了解更多的开发人员提供阅读列表。

7
您可能需要查看“Pragmatic Unit Testing”系列。 此处是C#版本,但也有Java版本。
关于您的规格,我不会过分纠结。 您已经有了一个非常好的开始-命名约定非常重要。 我们还要求目录结构与原始项目匹配。 覆盖范围还需要涵盖边界情况和非法值(检查异常)。 这很明显,但您的规格书是将其写下来以备将来不可避免地与那些不想为检查非法值的人争论的地方。 但不要使规格书超过几页,否则没有人会将其用于如此依赖上下文的任务。
更新:我不同意土豆先生关于每个单元测试只有一个assert的观点。 理论上听起来很好,但在实践中,这会导致大量大多数冗余测试或者人们在设置和拆卸中进行大量工作,这本身应该被测试。

我认为一个简单的标准集可以轻松地放在一页上(不包括示例)。也许两页。 - Gerrie Schenck
当你说你的文件“超过几页”时,是指3页吗? :) - Gerrie Schenck
嗯,我的不算是一页。我的观点是,在两页之后,我预测合规率会急剧下降。开发人员只喜欢以一般性的术语告诉他们如何开发,而不喜欢被告知具体细节。他们是(或应该是)专业人士。 - Mark Brittingham

5
我遵循TDD的BDD风格。参见:http://blog.daveastels.com/files/BDD_Intro.pdfhttp://dannorth.net/introducing-bddhttp://behaviour-driven.org/Introduction
简而言之,这意味着
  • 测试不是被视为“测试”,而是作为系统行为的规范(下文称为“规范”)。规范的目的不是验证系统在每种情况下都能正常工作。它们的目的是指定行为并推动系统设计。

  • 规范方法名称应写成完整的英语句子。例如,球的规范可以包括“球是圆的”,“当球撞到地板时,它会弹起”等。

  • 生产类和规范类之间没有强制的1:1关系(为每个生产方法生成一个测试方法是疯狂的)。相反,系统行为与规范之间存在1:1的关系。

一段时间前,我编写了TDD教程(从提供的测试开始编写俄罗斯方块游戏),演示了将测试编写为规范的风格。您可以从http://www.orfjackal.net/tdd-tutorial/tdd-tutorial_2008-09-04.zip下载它。有关如何进行TDD/BDD的说明仍然缺失,但示例代码已准备好,因此您可以查看测试的组织方式并编写通过它们的代码。
您会注意到,在本教程中,生产类的名称为Board、Block、Piece和Tetrominoe,围绕俄罗斯方块游戏的概念展开。但测试类是围绕俄罗斯方块游戏的行为展开的:FallingBlocksTest、RotatingPiecesOfBlocksTest、RotatingTetrominoesTest、FallingPiecesTest、MovingAFallingPieceTest、RotatingAFallingPieceTest等。

4
  1. 尽可能在每个测试方法中使用较少的断言语句。这可以确保测试的目的明确。
  2. 我知道这可能会引起争议,但是不要测试编译器 - 测试Java Bean访问器和变异器所花费的时间最好用于编写其他测试。
  3. 尽可能使用TDD而不是在编写代码后编写测试。

1
1 看起来有点严厉。假设我想测试一个新构建的 Vector3 是否所有组件都初始化为零?你真的会让我为每个组件编写单独的测试,还是更喜欢使用 assert(v.x()==0.0 && v.y()==0.0 && v.z()==0.0) 而不是为每个组件编写单独的 assert? - timday
你不应该使用assertEquals(new Vector3(0.0, 0.0, 0.0), (vector)),将构造函数中传入的值通过x()、y()和z()方法进行测试应该由微软的某个人来完成... - David Grant
首先,微软有什么关系呢?如果Vector3是一个自定义类(即不是语言运行时的一部分),您是说要通过创建一个新实例并访问属性来测试构造函数,以提取每个部分并进行验证? - Scott Dorman
如果您的属性设置错误会发生什么?唯一“正确”处理此类情况的方法是在测试构造函数之前测试属性,这很难做到,除非属性是可设置的,并且需要对测试进行明确排序。 - Scott Dorman
@Scott:我提到Microsoft是因为那是我能找到的唯一一个Vector3实例 - 我想我是想说,如果你正在使用别人编写的类,那么通常不会考虑测试他们的代码。 - David Grant
显示剩余2条评论

3
我发现大多数测试惯例可以通过为所有测试使用标准基类来强制执行。强制测试人员重写方法,以使它们具有相同的名称。
我还倡导安排-操作-断言(AAA)测试风格,因为您可以从测试中生成相当有用的文档。它还会迫使您考虑由于命名风格而期望的行为是什么。

你有关于AAA风格的更多信息吗?有任何文章链接吗? - Gerrie Schenck

1

你可以在你的标准中加入另一个条目,尽量保持你的单元测试代码规模小。也就是说,实际的测试方法本身。除非你正在进行完整的集成单元测试,否则通常不需要大型单元测试,比如超过100行。如果你有很多设置要到达你的一个测试,我会给你那么多。但是如果你确实这样做了,你应该考虑重构它。

人们还谈论重构他们的代码,确保人们意识到单元测试也是代码。所以重构、重构、重构。

我发现我所见过的用例中最大的问题是人们不倾向于认识到你想要保持你的单元测试轻盈和敏捷。毕竟,你不想为你的测试创建一个庞大的单体。考虑到这一点,如果你有一个你想要测试的方法,你不应该在一个单元测试中测试每个可能的路径。你应该有多个单元测试来考虑方法的每个可能路径。

是的,如果你正确地进行单元测试,你的单元测试代码行数平均应该比你的应用程序多。虽然这听起来像是很多工作,但当不可避免的业务需求变化到来时,它将为你节省很多时间。


1

确保包含非单元测试内容。请参阅:关于单元测试,不应该测试什么?

包括指南,以便可以清楚地识别集成测试并可从单元测试中单独运行。这很重要,因为如果将单元测试与其他类型的测试混合在一起,则可能会得到一组非常慢的“单元”测试。

有关更多信息,请查看此处:如何改进我的junit测试...特别是第二次更新。


1

使用全功能集成开发环境的用户会发现,“其中一些”提供了相当详细的支持,以特定模式创建测试。给定这个类:

public class MyService {
    public String method1(){
        return "";
    }

    public void method2(){

    }

    public void method3HasAlongName(){

    }
}

当我在Intellij IDEA中按下ctrl-shift-T时,回答1个对话框后,我会得到这个测试类:
public class MyServiceTest {
    @Test
    public void testMethod1() {
        // Add your code here
    }

    @Test
    public void testMethod2() {
        // Add your code here
    }

    @Test
    public void testMethod3HasAlongName() {
        // Add your code here
    }
}

因此,在编写标准之前,您可能需要仔细查看工具支持。


1

我在单元测试函数名称中使用几乎是简单的英语。这有助于明确定义它们确切的功能:

TEST( TestThatVariableFooDoesNotOverflowWhenCalledRecursively )
{
/* do test */
}  

我使用C++,但命名规范可以应用于任何地方。


0

如果您正在使用Junit(OCunit、SHunit等)工具系列,测试名称已遵循一些规则。

对于我的测试,我使用自定义的doxygen标签,以便将它们的文档收集到特定页面中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接