命名单元测试类和测试方法的最佳实践是什么?
这在前面已经讨论过了,单元测试的一些流行命名约定是什么?
我不知道这是否是一个非常好的方法,但是目前在我的测试项目中,我在每个生产类和一个测试类之间有一对一的映射,例如Product和ProductTest。
然后,在我的测试类中,我有一些方法,这些方法的名称是我要测试的方法的名称、一个下划线,然后是情况和我期望发生的事情,例如Save_ShouldThrowExceptionWithNullName()。
- 请参见:stackoverflow.com/questions/96297/…
- 这并不能回答你的问题,但值得一读:haacked.com/archive/2012/01/02/structuring-unit-tests.aspx
- 谷歌风格指南上说:test_,比如testPop_emptyStackgoogle-style guide.googlecode.com/svn/trunk/javaguide.html 5.2.3方法名。如果有疑问,请关注谷歌。
- @Cirosantilli六个四件法轮功包轩,下一句话说:"没有一个正确的方法来命名测试方法"。算了吧。
- 谷歌Java风格指南现在在这里:GoGoLe.Github. I/StuleGuID/JavaGuID.HTML。搜索"测试"。
- 即使几年后,我还是更喜欢菲尔·哈克的建议。
- 这可能会让你们中的一些人感兴趣。如果您碰巧使用xunit,我编写了一个包autoname.xunit,它将把任何定义的测试名称转换成一个简单的旧英语句子,即MyTestName将显示为My Test Name。
我喜欢Roy Osherove的命名策略,如下所示:
[工作单元状态预计行为]
它以结构化的方式提供了方法名所需的所有信息。
工作单元可以小到一个方法、一个类,也可以大到多个类。它应该表示在这个测试用例中要测试并且处于控制之下的所有事情。
对于程序集,我使用典型的.Tests结尾,我认为这种结尾非常普遍,对于类(以Tests结尾)也是如此:
[分类测试名称]
以前我使用fixture作为后缀而不是测试,但我认为后者更常见,然后我更改了命名策略。
- 对我来说,在测试方法中输入方法名是没有意义的。如果重命名方法呢?重构工具不会为您重命名测试。最终,您会手动重命名测试方法,或者更可能是错误地命名了测试。就像有评论一样。更糟糕的是根本不评论代码。
- @佩里:很有洞察力。类名也会出现同样的问题。假设可以将测试放在一个简单命名为"test"的私有嵌套类中,但没有单元测试框架支持这一点,而且大多数程序员都拒绝提供单元测试代码。我曾经编写过一个单元测试来反映所有的单元测试,并断言它们的名称是正确的,但是已经丢失了代码。
- @佩里,我认为这是一种权衡。一方面,您的测试名称可能会过时,另一方面,您无法确定测试的方法。我发现后者出现得更频繁。
- 除Peri的评论外,所有方法都负责某些操作,例如UpdateManager.Update()。考虑到这一点,我倾向于称我的测试为WhenUpdating_State_Behaviour或WhenUpdating_Behaviour_State。通过这种方式,我测试类的特定操作,同时避免将方法名放入测试名中。但最重要的是,当我看到一个失败测试的名称时,我必须知道什么业务逻辑失败了。
- 如果使用这些工具重构/重命名,resharper和intellij可能都会找到您的测试方法,并提议为您重命名它。还可以尝试在注释中查找您提到方法名的地方,并对其进行更新。
- @拉蒙娜:我同意你的看法。使用希望测试的操作作为测试名称的一部分,而不是可能更改的方法名称,这是非常合乎逻辑的。
- 我实际上点击了链接,与这个答案和建议的用法有偏差。它应该是不需要方法名的[UnitOfWork_StateUnderTest_ExpectedBehavior]。
- 好的方法名称通常与方法执行的操作相同。如果必须在方法之后命名测试或方法执行的操作之间做出决定,这可能是一个提示,您应该重命名方法。(但并非所有情况下都如此)
- 我认为测试名称中的方法名称没有问题。如果API发生变化,您将为这些新方法编写新的测试。旧测试将保留并测试不推荐使用的API,直到将其删除。如果在不向后兼容的情况下重命名公共方法,则必须更改测试以使用新方法(即添加新方法/注销旧方法的快捷方式)。重命名测试是其中最安全的部分。
- @peri-相反,在测试名称中包含方法名是有意义的(当您测试一个单独的方法时-通常是这样),因为这就是测试所要测试的内容。它是描述性的,对那些阅读测试套件的人有帮助。是的,它将测试名与方法名耦合在一起,但是重命名测试很简单,风险很低。在测试名称中包含被测方法的名称只反映了测试名称和被测方法名称之间的高度内聚性。
- @本-我不同意。方法名在测试中最不重要。测试功能。重命名测试和更新注释一样简单:)但是人们不这么做!这就是为什么评论也很烂的原因!
- @Peri将测试名称与功能性以外的任何东西分离是理想的。好处包括较低的维护O'head、较短的测试名称以及避免1类误导性测试名称。然而,当在团队测试中工作时,经验告诉我,这通常是非常不完美的。识别测试元素(如arr./act/断言)可能很困难。此外,测试命名通常很差(即使是以母语为母语的人),而且通常具有完全的误导性。因此,我倾向于此ans中的命名约定,这迫使作者清楚地思考正在测试的单元,从而缩短读者理解的时间。
- @本,我认为教你的团队写好的测试然后使用更差的命名约定要好得多,因为你的团队写坏的测试。我不接受测试中的安排/行动/断言评论。如果你需要他们,那意味着你的测试很糟糕。这是我的意见。
- 不太喜欢这个方法,但是内容很有趣。谢谢,+ 1
- UnitTest中的方法名看起来有噪音。为什么不编写一个看起来像业务逻辑语句的单元测试呢?例如,我的一个测试方法如下:"所有事件集合的概率之和必须等于‌&8203;_to_1()。因此,单元测试类应该充满业务逻辑语句。我们编写单元测试来覆盖所有业务逻辑。为什么要关心哪种方法执行任务,只要涵盖了所有业务逻辑。不管怎样,你应该让你的班级承担非常有限的责任(单一责任原则),这样就不难找出哪种方法涵盖了什么。
- 测试名称中的方法名称?不是你的江户十一〔0〕!
- 我喜欢这样:简而言之;使用单元测试;)
- 马克,这是一个很好的答案。但是,您似乎使用了双下划线,而您引用的链接使用了单下划线。你能详细解释一下吗?
- @我将bug改为一个下划线,因为它实际上反映了我如何命名它们(我从未使用过双下划线)。谢谢你指出。
- 基于行为的测试名称imo是避免逻辑或标识符更改时出现脆弱测试的最佳方法,因为方法的工作方式没有被测试,而是预期的行为。
- @皮奥特帕拉克,那你建议什么样的选择呢?由于重构问题,我也不喜欢在测试方法名中使用方法名。但是我仍然希望能够以某种方式将它追溯到正在测试的方法中,也许是通过引用或其他任何方式。你有什么建议吗?
- @pijei我的测试名称说明了关于被测系统(SUT)的事实。它可以很好地与Xunit一起使用,Xunit具有[fact]属性而不是[test]。例如,对于StringCalculator测试,可以命名为"计算和"、"添加数字"或"对负数字抛出"。方法被称为sum()、calculate()或addnumbers()并不重要。如果我重命名它,关于我的SUT的事实仍然有效,所以我不需要重命名测试。
我喜欢遵循测试的"应该"命名标准,同时在被测单元(即类)之后命名测试夹具。
举例说明(使用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()
- 也许更好,也不那么多余,只要写一个句子来说明它的作用,假设测试有效:increasesBalanceWhenDepositIsMade()。
- 最近看到一篇文章提到了一个类似的命名约定(希望我能给它加上书签)。设计用于使测试列表在按测试夹具排序时非常可读。你看到类似"银行账户"的东西,然后在它下面(在不同的行上)"应该在存款时增加余额,在取款时减少余额",等等,读起来非常像一个规范,这就是TDD的全部内容。
- 找到文章了。这是在贾斯汀·埃瑟利奇(JustinEtheredge)的共同思考的博客中,开始嘲笑第三部——第1部分。
- 你可能想看看这个fluentassertions.com
- 我也用"应该"和"何时",但反过来。例如,当ncustomerUnotexist_shouldthrowException()时。对我来说,这比当时更合理(即在某种情况下,应该有某种预期的结果)。这也适用于aaa(arrange、act、assert)…assert在末尾,而不是开头;-)
- 另请参见:ardalis.com/unit-test-naming-convention注意,最好将should作为后缀放在类名上,以便类名与sut名称不匹配。这些天,我还倾向于为每个方法提供一个测试类,测试的是每种类型的文件夹,在这种情况下,文件夹名称通常是footst(而不是foo),以避免命名冲突。
- @施耐德:考虑到"应该"="推荐"然后是可选的,我想知道:在词汇上,最好使用"应该"="必须"然后是必需的/强制性的。例如,RFC在两者之间都有区别。所以建议或要求通过测试?
- 我不明白这里下划线的用法。格式为[UnitOfWork _StateUnderest _ExpectedBehavior]的下划线的使用可以解释为用于在这三个概念之间引入另一个层次的分离,如果单独使用camel case,这就不那么明显了。但在您的例子中,camel的情况就足够了,这不是C推荐的命名约定吗?
- 在我看来,这不一定是:)
我喜欢这种命名方式:
1 2
| OrdersShouldBeCreated();
OrdersWithNoProductsShouldFail(); |
等等。对于非测试人员来说,它非常清楚问题是什么。
- 但是@hotshot309,他可能在使用.NET-.NET大小写约定
- @艾斯,我完全同意你的看法,在我发表这篇评论的一分钟后就注意到了这一点。我发誓看到我的错误就删除了它,但不知怎么的,我想我没有删除。对此我很抱歉。
- 那为什么不用下划线分开呢,你用的是和罗伊·奥一样的模式。
- @coffeeaddict,因为标识符中的下划线是一个
abnormablenot really惯用的C语言#
- 我也喜欢避免使用should,我更喜欢will,所以OrdersWithNoProductsWillFail()。
- 简单明了,我喜欢。然而,我希望Java可以有一种方法将测试分组在一起。
- 我不喜欢这个命名约定。例如:"ordersshouldbecreated"没有关于将在其中创建订单的场景(排列)的真正详细信息。如果有多个场景,因此有多个测试呢?您不能具有相同的测试方法名称…
- 你可以把它用在一般情况下。对于其他场景,您将有一个更具体的名称。
- @在我看来,使用will并不真正合适,这样做实际上是错误地告诉读者,考试决不会失败……如果你用will来表达将来可能不会发生的事情,你就是在错误地使用它,而should是更好的选择,因为它表明你想要/希望发生一些事情,但它没有或不能发生,当测试运行时,它会告诉你它是否失败/成功,所以你不能真正地预先暗示,不是你的逻辑解释是什么?你为什么要避开should?
- 方法不应该以小写字母开始,如"ordersShouldbecreated();"?
- @Zon它取决于所选语言的命名约定。在C中,它以大写字母开头,就像一个类。
- Zon,这是Java的命名约定。C是帕斯卡酶。
Kent Beck建议:
例如,具有以下类:
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() { ... }
} |
- 我希望更多的人遵守这些准则。不久前,我不得不重命名20多个测试方法,因为它们的名称类似于"atest"、"basictest"或"errortest"。
- 给定类的后缀,"test"的方法前缀不是多余的吗?
- 我想添加testdox的兼容性。agiledox.sourceforge.net网站
- 记住当肯特写那本书的时候。属性不是发明出来的。因此,方法名称中的名称测试向测试框架指示该方法是一个测试。自2002年以来也有很多事情发生。
- 测试计算…对于您的测试方法来说,这是一个毫无意义的名称。""测试"是多余的(您是否用"方法"前缀命名所有方法?)。名称的其余部分没有要测试的条件或预期的条件。计算方法在试验中吗?……谁知道……
- 我想补充一点,当使用这个策略时,需要文档来指定预期的输出。作为关于"test"前缀的补充说明,一些单元测试框架需要特定的前缀或后缀来识别测试。用"abstract"前缀抽象类不被认为是多余的(因为它是自记录的),所以为什么"test"不同样适用呢?
- @托马斯杰斯佩森是真的,但现在并没有使批评变得不那么有效。不再需要使用前缀。
类名。对于测试夹具名称,我发现"测试"在许多领域的普遍语言中非常常见。例如,在工程领域:StressTest,在化妆品领域:SkinTest。很抱歉不同意肯特的观点,但是在我的测试夹具中使用"test"(StressTestTest)。令人困惑。
"单位"在领域中也被大量使用。如MeasurementUnit。一个叫做MeasurementUnitTest的类是"测量"还是"测量单位"的测试?
因此,我喜欢对所有测试类使用"qa"前缀。例如:QaSkinTest和QaMeasurementUnit。它永远不会与域对象混淆,使用前缀而不是后缀意味着所有的测试设备都是可视化的(如果您的测试项目中有伪造的或其他支持类,则很有用)。
命名空间。我在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中使用它而不做任何更改。
- 如果当时的情况与方法名"场景"预期的行为相同,不是吗?!
- 不完全是这样。如果"当"指的更多,那么"当"指的是"当"指的是"当"指的是"当"指的是"当"指的是"当"指的是"当"指的是"当"指的是"当"指的是"当"指的是"当"指的是"当"指的是"当"指的是"当"指的是"发生"
- "那时候给"通常被称为小黄瓜。它是一个DSL,来自Cucumber、JBehave和Behat工具。
- +1这是imo中最好的方法。将一个方法与您期望的结果脱钩是非常强大的,可以避免其他注释中描述的许多问题。
见: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"中描述的最佳实践…很难打败朱尼特的合著者写的书!
- 不相信这真的回答了手头的问题-你能做一个编辑和参考Junit口袋指南吗?谢谢!
在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/。