我应该更改单元测试的命名约定吗?
-
06-07-2019 - |
题
我目前对单元测试使用一个简单的约定。如果我有一个名为“EmployeeReader”的类,我将创建一个名为“EmployeeReader.Tests”的测试类。然后,我在测试类中为该类创建所有测试,名称如下:
- Reading_Valid_Employee_Data_Correctly_Generates_Employee_Object
- Reading_Missing_Employee_Data_Throws_Invalid_Employee_ID_Exception
等等。
我最近读到一篇关于 不同类型的命名约定 用于BDD。我喜欢这个命名的可读性,最终得到一个测试列表,如下所示:
- When_Reading_Valid_Employee(夹具)
- Employee_Object_Is_Generate(方法)
- Employee_Has_Correct_ID(方法)
- When_Reading_Missing_Employee(固定装置)
- An_Invalid_Employee_ID_Exception_Is_Thrown(方法)
等等。
有人使用过这两种命名方式吗?您能提供任何建议、优点、缺点、陷阱等吗?帮助我决定是否切换到我的下一个项目?
解决方案
您的第二个示例(为每个逻辑“任务”提供一个固定装置,而不是为每个类提供一个固定装置)的优点是您可以为每个任务使用不同的 SetUp 和 TearDown 逻辑,从而简化您的单独测试方法并使它们更具可读性。
您无需选择其中之一作为标准。我们混合使用两者,具体取决于我们必须为每个类测试多少个不同的“任务”。
其他提示
我一直使用的命名约定是:
functionName_shouldDoThis_whenThisIsTheSituation
例如,这些将是堆栈的“pop”函数的一些测试名称
pop_shouldThrowEmptyStackException_whenTheStackIsEmpty
pop_shouldReturnTheObjectOnTheTopOfTheStack_whenThereIsAnObjectOnTheStack
我觉得第二个更好,因为它使您的单元测试对其他人更具可读性,因为长行使代码看起来更难以阅读或使其更难以浏览。如果您仍然觉得测试的作用有任何含糊之处,您可以添加注释来澄清这一点。
您引用的第二个命名约定背后的部分原因是您正在同时创建测试和行为规范。您建立了事情发生的背景以及在该背景下实际应该发生的事情。(根据我的经验,观察/测试方法通常以“should_”开头,因此您会得到标准的“When_the_invoicing_system_is_told_to_email_the_client”、“should_initiate_connection_to_mail_server”格式。)
有些工具可以反映您的测试装置并输出格式良好的 html 规格表,去掉下划线。您最终会得到与实际代码同步的人类可读文档(只要您保持测试覆盖率高且准确)。
根据您正在处理的故事/功能/子系统,这些规范可以向非程序员利益相关者展示和理解,以进行验证和反馈,这尤其是敏捷和 BDD 的核心。
我使用第二种方法,它确实有助于描述您的软件应该做什么。我还使用嵌套类来描述更详细的上下文。
本质上,测试类是上下文,可以嵌套,方法都是一行断言。例如,
public class MyClassSpecification
{
protected MyClass instance = new MyClass();
public class When_foobar_is_42 : MyClassSpecification
{
public When_foobar_is_42() {
this.instance.SetFoobar( 42 );
}
public class GetAnswer : When_foobar_is_42
{
private Int32 result;
public GetAnswer() {
this.result = this.GetAnswer();
}
public void should_return_42() {
Assert.AreEqual( 42, result );
}
}
}
}
这将在我的测试运行器中提供以下输出:
MyClassSpecification+When_foobar_is_42+GetAnswer
should_return_42
我一直沿着你在问题中描述的两条路以及其他一些路走...您的第一个选择对于大多数人来说非常简单且易于理解。我个人更喜欢 BDD 风格(你的第二个例子),因为它隔离了不同的上下文并对这些上下文的观察进行分组。唯一真正的缺点是它会生成更多代码,因此开始执行会感觉稍微麻烦一些,直到您看到整洁的测试为止。此外,如果您使用继承来重用固定装置设置,您需要一个输出继承链的测试运行器。考虑一个类“An_empty_stack”,并且您想重用它,因此您可以执行另一个类:“When_ Five_is_pushed_on :An_empty_stack”您希望将其作为输出,而不仅仅是“When_ Five_is_pushed_on”。如果您的测试运行程序不支持这一点,您的测试将包含冗余信息,例如:“When_ Five_is_pushed_on_empty_stack:An_empty_stack”只是为了使输出更好。
我投票赞成调用测试用例类:EmployeeReaderTestCase并调用methods(),例如 http://xunitpatterns.com/Organization.html 和 http://xunitpatterns.com/Organization.html#Test%20Naming%20Conventions