命名单元测试类和测试方法的最佳实践是什么?
之前在 SO 上对此进行了讨论,在单元测试的一些流行的命名约定是什么?
我不知道这是否是一个很好的方法,但目前在我的测试项目中,我在每个生产类和测试类之间都有一对一的映射,例如Product
和ProductTest
.
然后,在我的测试类中,我的方法的名称是我正在测试的方法的名称、下划线,然后是情况和我期望发生的情况,例如Save_ShouldThrowExceptionWithNullName()
.
命名单元测试类和测试方法的最佳实践是什么?
之前在 SO 上对此进行了讨论,在单元测试的一些流行的命名约定是什么?
我不知道这是否是一个很好的方法,但目前在我的测试项目中,我在每个生产类和测试类之间都有一对一的映射,例如Product
和ProductTest
.
然后,在我的测试类中,我的方法的名称是我正在测试的方法的名称、下划线,然后是情况和我期望发生的情况,例如Save_ShouldThrowExceptionWithNullName()
.
更新(2021 年 7 月)
自从我最初的答案(将近 12 年)和最佳实践在此期间发生了很大变化以来,已经有一段时间了。所以我倾向于更新我自己的答案,并为读者提供不同的命名策略。
许多评论和答案指出,我在原始答案中提出的命名策略并不抗拒重构,最终导致难以理解的名称,我完全同意。
在过去的几年里,我最终使用了一种更易于阅读的命名模式,其中测试名称描述了我们想要测试的内容,正如Vladimir Khorikov所描述的那样。
一些例子是:
Add_credit_updates_customer_balance
Purchase_without_funds_is_not_possible
Add_affiliate_discount
但正如您所看到的,它是一个非常灵活的模式,但最重要的是,阅读名称您就知道测试是关于什么的,而不包括可能随时间变化的技术细节。
为了命名项目和测试类,我仍然坚持原来的答案模式。
原始答案(2009 年 10 月)
我喜欢Roy Osherove 的命名策略。如下:
[UnitOfWork_StateUnderTest_ExpectedBehavior]
它具有方法名称和结构化方式所需的所有信息。
工作单元可以小到单个方法、一个类,也可以大到多个类。它应该代表所有要在这个测试用例中测试并在控制之下的东西。
对于程序集,我使用典型的.Tests
结尾,我认为这种结尾非常普遍,并且对于类也是如此(以 结尾Tests
):
[NameOfTheClassUnderTestTests]
之前我是用Fixture作为后缀而不是Tests,但我觉得后者更常见,于是我改变了命名策略。
我喜欢遵循测试的“应该”命名标准,同时在被测单元(即类)之后命名测试夹具。
为了说明(使用 C# 和 NUnit):
[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));
}
}
为什么是“应该”?
我发现它迫使测试编写者用类似于“应该[处于某种状态] [之后/之前/何时] [动作发生]”的句子来命名测试
是的,到处写“应该”确实有点重复,但正如我所说,它迫使作家以正确的方式思考(这对新手有好处)。另外,它通常会产生一个可读的英文测试名称。
更新:
我注意到 Jimmy Bogard 也是“应该”的粉丝,甚至还有一个名为“应该”的单元测试库。
更新(4年后...)
对于那些感兴趣的人,我命名测试的方法多年来一直在发展。我在上面描述的应该模式的一个问题是它不容易一眼就知道正在测试哪种方法。对于 OOP,我认为使用被测方法开始测试名称更有意义。对于一个设计良好的类,这应该导致可读的测试方法名称。我现在使用类似于<method>_Should<expected>_When<condition>
. 显然,根据上下文,您可能希望将“应该/何时”动词替换为更合适的动词。例子:
Deposit_ShouldIncreaseBalance_WhenGivenPositiveValue()
我喜欢这种命名风格:
OrdersShouldBeCreated();
OrdersWithNoProductsShouldFail();
等等。它让非测试人员非常清楚问题是什么。
肯特贝克建议:
每个“单元”(程序的类)一个测试夹具。测试装置本身就是类。测试夹具名称应为:
[name of your 'unit']Tests
测试用例(测试夹具方法)的名称如下:
test[feature being tested]
例如,具有以下类:
class Person {
int calculateAge() { ... }
// other methods and properties
}
测试夹具将是:
class PersonTests {
testAgeCalculationWithNoBirthDate() { ... }
// or
testCalculateAge() { ... }
}
类名。对于测试夹具名称,我发现“测试”在许多领域的通用语言中很常见。例如,在工程领域:StressTest
,在化妆品领域:SkinTest
。很抱歉不同意 Kent,但在我的测试装置(StressTestTest
?)中使用“测试”会令人困惑。
“单位”在域中也被大量使用。例如MeasurementUnit
。一个类是称为MeasurementUnitTest
“Measurement”还是“MeasurementUnit”的测试?
因此,我喜欢对所有测试类使用“Qa”前缀。例如QaSkinTest
和QaMeasurementUnit
。它永远不会与域对象混淆,并且使用前缀而不是后缀意味着所有测试装置在视觉上都在一起(如果您的测试项目中有假货或其他支持类,则很有用)
命名空间。我在 C# 中工作,并将我的测试类保存在与他们正在测试的类相同的命名空间中。它比拥有单独的测试命名空间更方便。当然,测试类在不同的项目中。
测试方法名称。我喜欢将我的方法命名为WhenXXX_ExpectYYY。它使前提条件清晰,并有助于自动化文档(a la TestDox)。这与 Google 测试博客上的建议类似,但前提条件和期望更加分离。例如:
WhenDivisorIsNonZero_ExpectDivisionResult
WhenDivisorIsZero_ExpectError
WhenInventoryIsBelowOrderQty_ExpectBackOrder
WhenInventoryIsAboveOrderQty_ExpectReducedInventory
我使用Given-When-Then概念。看看这篇短文http://cakebaker.42dh.com/2009/05/28/given-when-then/。文章以 BDD 的形式描述了这个概念,但您也可以在 TDD 中使用它而无需任何更改。
我最近提出了以下约定来命名我的测试、它们的类和包含项目,以最大化它们的描述性:
假设我正在命名空间Settings
中的项目中测试该类MyApp.Serialization
。
首先,我将使用MyApp.Serialization.Tests
命名空间创建一个测试项目。
在这个项目中,当然还有命名空间中,我将创建一个名为IfSettings
(保存为IfSettings.cs)的类。
可以说我正在测试该SaveStrings()
方法。-> 我将命名测试CanSaveStrings()
。
当我运行此测试时,它将显示以下标题:
MyApp.Serialization.Tests.IfSettings.CanSaveStrings
我认为这很好地告诉了我,它正在测试什么。
当然,在英语中,名词“Tests”与动词“tests”相同是很有用的。
您在命名测试方面的创造力没有限制,因此我们可以获得完整的句子标题。
通常测试名称必须以动词开头。
示例包括:
DetectsInvalidUserInput
)ThrowsOnNotFound
)WillCloseTheDatabaseAfterTheTransaction
)等等
另一种选择是使用“that”而不是“if”。
后者虽然节省了我的击键次数,并且更准确地描述了我正在做的事情,因为我不知道测试的行为是否存在,但我正在测试它是否存在。
[编辑]
在使用上述命名约定一段时间后,我发现在使用接口时, If前缀可能会令人困惑。碰巧的是,测试类IfSerializer.cs看起来与“打开文件选项卡”中的接口ISerializer.cs非常相似。当在测试、被测试的类及其接口之间来回切换时,这会变得非常烦人。因此,我现在会选择That over If作为前缀。
此外,我现在使用 - 仅用于我的测试类中的方法,因为它不被认为是其他任何地方的最佳实践 - 用于分隔我的测试方法名称中的单词的“_”,如下所示:
[Test] public void detects_invalid_User_Input()
我发现这更容易阅读。
[结束编辑]
我希望这会产生更多的想法,因为我认为命名测试非常重要,因为它可以为您节省大量时间,否则这些时间会花费在试图了解测试在做什么(例如,在长时间中断后恢复项目之后) .
请参阅:http: //googletesting.blogspot.com/2007/02/tott-naming-unit-tests-responsibly.html
对于测试方法名称,我个人发现使用详细和自记录的名称非常有用(以及进一步解释测试正在做什么的 Javadoc 注释)。
我认为最重要的事情之一是在您的命名约定中保持一致(并与您团队的其他成员达成一致)。很多时候,我看到在同一个项目中使用了许多不同的约定。
在 VS + NUnit 中,我通常在我的项目中创建文件夹来将功能测试组合在一起。然后我创建单元测试夹具类并以我正在测试的功能类型命名它们。[Test] 方法的命名方式如下Can_add_user_to_domain
:
- MyUnitTestProject
+ FTPServerTests <- Folder
+ UserManagerTests <- Test Fixture Class
- Can_add_user_to_domain <- Test methods
- Can_delete_user_from_domain
- Can_reset_password
我应该补充一点,将您的测试保存在同一个包中,但在与正在测试的源代码并行的目录中,一旦您准备好部署它,就可以消除代码的膨胀,而无需执行一堆排除模式。
我个人喜欢《JUnit 袖珍指南》中描述的最佳实践……很难打败 JUnit 的合著者写的书!