单元测试中常用的命名规范有哪些?

208

通用

  • 遵循所有测试的相同标准。
  • 明确每个测试状态是什么。
  • 具体说明期望的行为。

示例

1) 方法名_被测试状态_期望的行为

Public void Sum_NegativeNumberAs1stParam_ExceptionThrown() 

Public void Sum_NegativeNumberAs2ndParam_ExceptionThrown () 

Public void Sum_simpleValues_Calculated ()

来源: 单元测试的命名标准

2) 用下划线将每个单词分开

Public void Sum_Negative_Number_As_1st_Param_Exception_Thrown() 

Public void Sum_Negative_Number_As_2nd_Param_Exception_Thrown () 

Public void Sum_Simple_Values_Calculated ()

其他

  • 将方法名以Test结尾
  • 将方法名以类名开头

请参阅行为驱动开发 - Wedge
7个回答

98

我非常赞同你的想法。你所使用的命名约定如下:

  • 明确每个测试状态是什么。
  • 具体说明预期的行为。

从测试名称中,你还需要什么呢?

Ray's answer相反,我认为不需要在前面添加Test前缀。这是测试代码,我们都知道。如果你需要这样做来识别代码,那你有更大的问题,你的测试代码不应该混杂在生产代码中。

至于长度和下划线的使用,这是测试代码,谁会在乎呢?只有你和你的团队会看到它,只要它可读并且清楚地说明了测试正在做什么,就可以继续! :)

话虽如此,我对测试还是很新,也在博客上记录我的经历 :)


21
“只要可读且清晰”和“谁……在意”的说法似乎有些矛盾。但实际上,当内容无法被理解和清晰表达时,每个人都会关心它是否可读。这就是为什么它很重要的原因。 :-) - David Victor
1
一个前缀的额外参数。当你在IDE中搜索文件时,可以通过以“Test”和你的类名开头来轻松搜索测试用例。如果类名和测试类名相同,我们将不得不暂停并阅读两个文件的路径。 - THIS USER NEEDS HELP
1
@THISUSERNEEDSHELP 我认为你提出的问题可以通过良好的文件夹结构轻松解决,例如 src/libssrc/tests。我知道一些测试运行框架确实需要一个前缀来识别测试代码,比如 _test_,在这些情况下无法避免使用前缀,但对于其他情况,可以不必要地重复使用前缀。 - negrotico19
@negrotico19 我在考虑像在IntelliJ中使用“搜索所有”(shift shift)或“按名称查找类”(CMD O)的情况。我知道它将通过文件夹结构或模块结构进行区分,但当我们搜索某些内容时,我们已经知道要搜索什么。例如,如果我正在寻找一个测试,我想将我的搜索限制在“test”上,然后再搜索名称,而不是先搜索名称,然后通过眼睛手动过滤出测试。这是一个小区别,但更容易“测试[类名]”,只有一个弹出并减少心理负担。 - THIS USER NEEDS HELP

38

这也值得一读:单元测试结构

该结构每个被测试类都有一个测试类,这并不罕见。但对我来说不寻常的是,他为每个被测试方法都有一个嵌套类。

例如:

using Xunit;

public class TitleizerFacts
{
    public class TheTitleizerMethod
    {
        [Fact]
        public void NullName_ReturnsDefaultTitle()
        {
            // Test code
        }

        [Fact]
        public void Name_AppendsTitle()
        {
            // Test code
        }
    }

    public class TheKnightifyMethod
    {
        [Fact]
        public void NullName_ReturnsDefaultTitle()
        {
            // Test code
        }

        [Fact]
        public void MaleNames_AppendsSir()
        {
            // Test code
        }

        [Fact]
        public void FemaleNames_AppendsDame()
        {
            // Test code
        }
    }
}

这是为什么:

首先,这是一种很好的方式来保持测试有序。方法的所有测试(或事实)都被分组在一起。例如,如果您使用CTRL+M、CTRL+O快捷键折叠方法主体,您可以轻松地扫描测试并像代码规范一样阅读它们。

我也喜欢这种方法:

MethodName_StateUnderTest_ExpectedBehavior

所以也许可以调整为:

StateUnderTest_ExpectedBehavior

因为每个测试已经在一个嵌套类中了。


2
对于在Visual Studio中使用Resharper测试运行程序的用户,他们在8.x版本中修复了嵌套测试类的错误。从那以后,这成为了我的首选结构。 - angularsen
1
采用MethodName_StateUnderTest_ExpectedBehavior的方式命名,方法名称会变得很长,这是否重要?例如"InitializeApiConfiguration_MissingApiKey_IllegalArgumentException"。这真的是一个好的测试名称吗? - portfoliobuilder

30

我倾向于使用以下命名规则:MethodName_DoesWhat_WhenTheseConditions,例如:

Sum_ThrowsException_WhenNegativeNumberAs1stParam
然而,我经常看到的做法是让测试名称遵循单元测试结构:

  • 安排(Arrange)
  • 操作(Act)
  • 断言(Assert)

这也遵循BDD/Gherkin语法:

  • 假如(Given)
  • 当(When)
  • 那么(Then)

因此,将测试命名为UnderTheseTestConditions_WhenIDoThis_ThenIGetThis的方式进行。例如:

WhenNegativeNumberAs1stParam_Sum_ThrowsAnException

不过我更喜欢把被测试的方法名称放在前面,因为这样可以按字母顺序排列测试,或在VisStudio的成员下拉框中按字母顺序排序,并且所有1个方法的测试都会被分组在一起。


无论如何,我喜欢用下划线将测试名称的主要部分分开,而不是每个单词,因为我认为这会使读者更容易阅读并理解测试的含义。

换句话说,我更喜欢:Sum_ThrowsException_WhenNegativeNumberAs1stParam 胜过于 Sum_Throws_Exception_When_Negative_Number_As_1st_Param


22

我像命名其他方法一样使用“帕斯卡命名法”来命名我的测试方法,没有下划线或分隔符。我留出方法后缀Test,因为它没有添加任何价值。通过属性TestMethod指示该方法是一个测试方法。

[TestMethod]
public void CanCountAllItems() {
  // Test the total count of items in collection.
}
由于每个测试类应该只测试一个其他类,我在方法名中留下了类的名称。包含测试方法的类的名称与被测试的类相同,后缀为“Tests”。
[TestClass]
public class SuperCollectionTests(){
    // Any test methods that test the class SuperCollection
}

对于测试异常或不可能发生的动作的方法,我会在测试方法前缀中加上单词Cannot

[TestMethod]
[ExpectedException(typeOf(ArgumentException))]
public void CannotAddSameObjectAgain() {
  // Cannot add the same object again to the collection.
}

我的命名规范基于Bryan Cook的文章“TDD技巧:测试命名约定和指南”。我发现这篇文章非常有帮助。


1
+1 链接到我的帖子——虽然在测试中不必使用“Test”前缀。确保您的测试指定了预期的行为。例如,当添加多个项目时,可以检索正确的计数。 - bryanbcook
2
我不喜欢它,因为它没有包含预期的行为。 - Johannes Rudolph

5

对我来说,第一组名称更易读,因为CamelCasing将单词分开,而下划线则分隔了命名方案的各个部分。

我也倾向于在函数名称或封闭的命名空间或类中包含“Test”。


2
@Frank methodName = 驼峰命名法 MethodName = 帕斯卡命名法 - Metro Smurf
@metro-smurf:有趣的区别,我从来没有听说过PascalCase这个术语,而我已经做了很长时间了。我只看到PascalCase这个术语在微软开发者圈子中出现,你是这样做的吗? - Frank Szczerba
在框架的最初设计中,我们进行了数百小时的关于命名风格的辩论。为了促进这些辩论,我们创造了许多术语。由于Anders Heilsberg(Turbo Pascal的原始设计师)是设计团队的重要成员,因此我们选择了Pascal Casing这个术语来表示Pascal编程语言所推广的大小写风格。 - Riegardt Steyn

-3
只要你遵循一项实践,就不会有太大问题。通常,我为一个方法编写一个单元测试,覆盖该方法的所有变化(我的方法很简单);然后为需要更复杂测试的方法编写更复杂的测试集。我的命名结构通常是 test(这是从 JUnit 3 中保留下来的)。

-7

我在测试命名空间、类和方法中使用“T”前缀。

我尽量保持整洁,创建复制命名空间的文件夹,然后为测试创建一个测试文件夹或单独的项目,并为基本测试复制生产结构:

AProj
   Objects
      AnObj
         AProp
   Misc
      Functions
         AFunc
   Tests
      TObjects
         TAnObj
            TAnObjsAreEqualUnderCondition
      TMisc
         TFunctions
            TFuncBehavesUnderCondition

我可以轻松地看出某些东西是一个测试,我知道它所涉及的原始代码是什么(如果你无法弄清楚,那么这个测试本来就太复杂了)。

它看起来就像接口命名约定一样(我的意思是,你不会因为以'I'开头的事物而感到困惑,也不会因为'T'而感到困惑)。

很容易只编译带或不带测试。

理论上来说很好,对于小项目来说效果还不错。


3
有趣的方法。有些人可能会认为 T 前缀与泛型中使用的约定(例如 func(T1, T2, TResult))存在冲突,但只要在团队内达成共识,我个人并不在意。这些名称很短,使得代码更易读。 - stung
对我来说,这个太匈牙利(表示法)了。另外,正如所述,前缀T用于通用类型参数。 - Danny Varod
我同意,匈牙利命名法已经被弃用了,并且由于与标准通用类型参数的冲突,我不认为在这种情况下会有例外适用(就像接口一样)。 - SonOfPirate

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