单元测试命名最佳实践

Unit test naming best practices

命名单元测试类和测试方法的最佳实践是什么?

这在前面已经讨论过了,单元测试的一些流行命名约定是什么?

我不知道这是否是一个非常好的方法,但是目前在我的测试项目中,我在每个生产类和一个测试类之间有一对一的映射,例如ProductProductTest

然后,在我的测试类中,我有一些方法,这些方法的名称是我要测试的方法的名称、一个下划线,然后是情况和我期望发生的事情,例如Save_ShouldThrowExceptionWithNullName()


我喜欢Roy Osherove的命名策略,如下所示:

[工作单元状态预计行为]

它以结构化的方式提供了方法名所需的所有信息。

工作单元可以小到一个方法、一个类,也可以大到多个类。它应该表示在这个测试用例中要测试并且处于控制之下的所有事情。

对于程序集,我使用典型的.Tests结尾,我认为这种结尾非常普遍,对于类(以Tests结尾)也是如此:

[分类测试名称]

以前我使用fixture作为后缀而不是测试,但我认为后者更常见,然后我更改了命名策略。


我喜欢遵循测试的"应该"命名标准,同时在被测单元(即类)之后命名测试夹具。

举例说明(使用c和nunit):

1
2
3
4
5
6
7
8
9
10
11
[TestFixture]
public class BankAccountTests
{
  [Test]
  public void Should_Increase_Balance_When_Deposit_Is_Made()
  {
     var bankAccount = new BankAccount();
     bankAccount.Deposit(100);
     Assert.That(bankAccount.Balance, Is.EqualTo(100));
  }
}

为什么"应该"?

我发现它迫使测试作者用一个句子来命名测试,"应该[处于某种状态][在某个状态][之后/之前/什么时候][动作发生]"

是的,在任何地方写"应该"都会有一点重复,但正如我所说,它迫使作者以正确的方式思考(所以对新手来说是好的)。另外,它通常会产生一个可读的英语测试名。

更新:

我注意到JimmyBogard也是"应该"的粉丝,甚至还有一个名为"应该"的单元测试库。

更新(4年后…)

对于那些感兴趣的人来说,我命名测试的方法在过去几年中不断发展。我上面描述的应该模式的一个问题是,一眼就不容易知道哪个方法正在测试中。对于OOP,我认为用测试中的方法来启动测试名称更有意义。对于设计良好的类,这将导致可读的测试方法名称。我现在使用类似于_Should_When的格式。显然,根据上下文的不同,您可能希望用should/when动词替换更合适的内容。例子:Deposit_ShouldIncreaseBalance_WhenGivenPositiveValue()


我喜欢这种命名方式:

1
2
OrdersShouldBeCreated();
OrdersWithNoProductsShouldFail();

等等。对于非测试人员来说,它非常清楚问题是什么。


Kent Beck建议:

  • 每个"单元"(程序类)一个测试夹具。测试夹具本身就是类。测试夹具名称应为:

    1
    [name of your 'unit']Tests
  • 测试用例(测试夹具方法)的名称如下:

    1
    test[feature being tested]

例如,具有以下类:

1
2
3
4
5
class Person {
    int calculateAge() { ... }

    // other methods and properties
}

测试夹具应为:

1
2
3
4
5
6
7
8
class PersonTests {

    testAgeCalculationWithNoBirthDate() { ... }

    // or

    testCalculateAge() { ... }
}


类名。对于测试夹具名称,我发现"测试"在许多领域的普遍语言中非常常见。例如,在工程领域:StressTest,在化妆品领域:SkinTest。很抱歉不同意肯特的观点,但是在我的测试夹具中使用"test"(StressTestTest)。令人困惑。

"单位"在领域中也被大量使用。如MeasurementUnit。一个叫做MeasurementUnitTest的类是"测量"还是"测量单位"的测试?

因此,我喜欢对所有测试类使用"qa"前缀。例如:QaSkinTestQaMeasurementUnit。它永远不会与域对象混淆,使用前缀而不是后缀意味着所有的测试设备都是可视化的(如果您的测试项目中有伪造的或其他支持类,则很有用)。

命名空间。我在C中工作,并且我将测试类保持在与它们测试的类相同的名称空间中。它比单独的测试名称空间更方便。当然,测试类在不同的项目中。

测试方法名称。我喜欢将我的方法命名为Whenxx_Expecty。它明确了前提条件,并有助于自动化文档(一个la testdox)。这类似于谷歌测试博客上的建议,但前提条件和期望之间有更多的分离。例如:

1
2
3
4
WhenDivisorIsNonZero_ExpectDivisionResult
WhenDivisorIsZero_ExpectError
WhenInventoryIsBelowOrderQty_ExpectBackOrder
WhenInventoryIsAboveOrderQty_ExpectReducedInventory


我使用了"给出时"的概念。看看这篇短文http://cakebaker.42dh.com/2009/05/28/given when then/。本文用BDD来描述这个概念,但是您也可以在TDD中使用它而不做任何更改。


见:http://googletesting.blogspot.com/2007/02/tott-naming-unit-tests-responsibly.html

对于测试方法名称,我个人认为使用详细的和自记录的名称非常有用(与进一步解释测试所做工作的JavaDoc注释一起使用)。


我认为最重要的一点是在命名约定中保持一致(并与团队的其他成员达成一致)。很多时候,我看到在同一个项目中使用了大量不同的约定。


我最近提出了以下命名我的测试、它们的类和包含项目的约定,以最大化它们的描述:

假设我正在测试myapp.serialization命名空间中项目中的设置类。

首先,我将使用myapp.serialization.tests命名空间创建一个测试项目。

当然,在这个项目中,我将创建一个名为ifsettings(另存为ifsettings.cs)的类。

假设我正在测试saveStrings()方法。->我将命名测试cansaveStrings()。

当我运行此测试时,它将显示以下标题:

myapp.serialization.tests.ifsettings.cansaveStrings

我认为这很好地告诉了我它在测试什么。

当然,在英语中,名词"tests"与动词"tests"相同是有用的。

在命名测试时,你的创造力是没有限制的,这样我们就可以得到完整的句子标题。

通常,测试名必须以动词开头。

示例包括:

  • 检测(例如检测有效输入)
  • 抛出(例如throwsonnotfound)
  • 将(例如,在传输后将关闭数据库)

等。

另一种选择是使用"that"而不是"if"。

不过,后者为我节省了击键次数,并更准确地描述了我正在做的事情,因为我不知道,测试的行为存在,但正在测试是否存在。

[编辑]

在使用了上述命名约定一段时间之后,我发现,使用接口时,if前缀可能会混淆。恰好如此,测试类ifserializer.cs看起来与"打开文件"选项卡中的接口iserializer.cs非常相似。当在测试、被测试的类及其接口之间来回切换时,这会非常恼人。因此,我现在选择if作为前缀。

此外,我现在使用-仅用于我的测试类中的方法,因为它不被认为是其他任何地方的最佳实践-在我的测试方法名称中分隔单词的"u",如下所示:

  • [测试]public void检测到_invoid_user_input()。*

我觉得这个更容易阅读。

[结束编辑]

我希望这会产生更多的想法,因为我认为命名测试非常重要,因为它可以节省很多时间,否则将花费大量时间来理解测试正在做什么(例如,在长时间中断后恢复项目之后)。


我应该补充一点,将您的测试保存在同一个包中,但是与被测试的源代码并行的目录中,可以在您准备好部署代码时消除代码膨胀,而无需执行一系列排除模式。

我个人喜欢"Junit Pocket Guide"中描述的最佳实践…很难打败朱尼特的合著者写的书!


在vs+nunit中,我通常在项目中创建文件夹,将功能测试分组在一起。然后我创建单元测试夹具类,并在我测试的功能类型之后命名它们。[测试]方法是按照Can_add_user_to_domain的行命名的:

1
2
3
4
5
6
- MyUnitTestProject  
  + FTPServerTests <- Folder
   + UserManagerTests <- Test Fixture Class
     - Can_add_user_to_domain  <- Test methods
     - Can_delete_user_from_domain
     - Can_reset_password


类foo的测试用例的名称应该是footscase或类似的名称(foointegrationtestcase或fooacceptiontestcase),因为它是一个测试用例。有关一些标准命名约定,如测试、测试用例、测试夹具、测试方法等,请参见http://xunitpatterns.com/。