分组相似的测试:是或不是

本文关键字:测试 相似 | 更新日期: 2023-09-27 17:52:37

我在某处读到每个测试必须只测试一个东西。但是,在良好实践手册中是否允许对类似的行为进行分组?我目前正在编写一些测试(c#与NUnit),下面是我所面临的一个例子:

[TearDown]
public void Cleanup()
{
    Hotkeys.UnregisterAllLocals();
    Hotkeys.UnregisterAllGlobals();
}
[Test]
public void KeyOrderDoesNotMatter()
{
    Hotkeys.RegisterGlobal("Ctrl+Alt+P", delegate { });
    Assert.That(Hotkeys.IsRegisteredGlobal("Alt+P+Ctrl"), Is.True);
}
[Test]
public void KeyCaseDoesNotMatter()
{
    Hotkeys.RegisterGlobal("Ctrl+Alt+P", delegate { });
    Assert.That(Hotkeys.IsRegisteredGlobal("ctrl+alt+p"), Is.True);
}
[Test]
public void KeySpacesDoesNotMatter()
{
    Hotkeys.RegisterGlobal("Ctrl+Alt+P", delegate { });
    Assert.That(Hotkeys.IsRegisteredGlobal("Ctrl + Alt + P"), Is.True);
}

分组后,它们将变成:

[TearDown]
public void Cleanup()
{
    Hotkeys.UnregisterAllLocals();
    Hotkeys.UnregisterAllGlobals();
}
[Test]
public void KeyIsNotStrict()
{
    // order
    Hotkeys.RegisterGlobal("Ctrl+Alt+A", delegate { });
    Assert.That(Hotkeys.IsRegisteredGlobal("Alt+A+Ctrl"), Is.True);
    // whitespace
    Hotkeys.RegisterGlobal("Ctrl+Alt+B", delegate { });
    Assert.That(Hotkeys.IsRegisteredGlobal("Ctrl + Alt + B"), Is.True);
    // case
    Hotkeys.RegisterGlobal("Ctrl+Alt+C", delegate { });
    Assert.That(Hotkeys.IsRegisteredGlobal("ctrl+alt+c"), Is.True);
}

那么什么是最佳实践(如果存在的话),为什么?

obs:我对单元测试比较陌生…

分组相似的测试:是或不是

简短的回答是否定的。您应该使您的测试尽可能简单。每个测试应该只测试一个东西。

单元测试有一个安排-行为-断言(AAA)模式。它表明您应该在测试方法的开始(安排)做一些准备,然后做出该测试检查的动作,并在方法的末尾做出一些断言。

我还建议您阅读关于单元测试的FIRST模式。

乌利希期刊指南:

当你让你的测试变得复杂时——当测试变成"红色"时很难识别出什么是错误的,你知道一些断言失败了,但是你必须阅读日志来理解哪一个。此外,如果复杂测试中的第一个断言失败,您甚至不知道其余断言是否正确。维护大型单元测试也很困难,您为第一个断言所做的一些工作可能会产生影响下一个断言的副作用。

但是你应该考虑到你关于GRASP的测试也应该是低耦合/高内聚的,所以,正如@Schwern在他的回答中提到的,如果不同的测试最终会测试相同的逻辑内容,你不应该为了最小化断言而编写分离的测试。在每个特定的情况下,哪种方式是正确的总是由开发人员自行决定的。

注意:我不是c#程序员。

一方面,你必须"在一次考试中只做一件事"。另一方面是DRY原则。你被要求违反哪一条。这取决于你违反这些规则的严重程度,你从违反这些规则中得到了多少好处,以及这些规则最初存在的原因。

你的分组解决方案不是理想的,因为它仍然重复自己。如果你这样做了…

[TearDown]
public void Cleanup()
{
    Hotkeys.UnregisterAllLocals();
    Hotkeys.UnregisterAllGlobals();
}
[Test]
public void IsRegisteredGlobal_InputNormalization()
{
    Hotkeys.RegisterGlobal("Ctrl+Alt+P", delegate { });
    Assert.IsTrue(Hotkeys.IsRegisteredGlobal("Alt+P+Ctrl"),     "order independent");
    Assert.IsTrue(Hotkeys.IsRegisteredGlobal("ctrl+alt+p"),     "case insensitive");
    Assert.IsTrue(Hotkeys.IsRegisteredGlobal("Ctrl + Alt + P"), "whitespace independent");
}

那么你就没有违反DRY,而且你几乎没有破坏"在测试中只做一件事"。它仍然在做一件事,那就是对IsRegisteredGlobal的输入进行规范化。

为了隔离它们,每个测试只做一件事。这使得测试更容易隔离和调试。这并不意味着每个测试都有一个断言。上面的测试仍然只做一件事,但它是用三种非常非常相似的方式进行测试的。这没关系。以前,您的断言是由测试名称解释的。现在,它们由与每个断言相关联的消息来解释。失败的原因很明显,就是FIRST中的I。

此外,如果您编写所有的测试时每个方法都有一个断言,并且不必要地一遍又一遍地重复相同的代码,您不仅违反了DRY,而且重复的代码可能会开始减慢速度,违反了FIRST中的F。

检查Hotkeys.IsRegisteredGlobal("Alt+P+Ctrl")是否可能导致Hotkeys.IsRegisteredGlobal("ctrl+alt+p")为真,而如果颠倒它们的顺序则不会为真?是的。但在隔离、速度和方便之间总是有权衡的。如果你怀疑一个人可能会干扰另一个人,你应该隔离他们。我想说的是,如果你想检查两者之间是否存在耦合,那么你应该在它自己的测试中显式地这样做,而不是让每个测试都承担这个负担。

是的,运行代码并对其进行多次断言是可以的,但请始终记住,这是好的代码和好的测试之间的平衡行为。通常测试是成功的,但不要犯傻。

我把它切换到IsTrue而不是通用的That紧凑,清晰和潜在的更好的故障诊断。Assert.That( thing, condition )意味着直到最后你才知道你在测试什么。你必须通读整行,看看条件是什么,然后根据你真正要测试的内容再通读一遍。Assert.IsTrue在前面告诉你。Assert.That在复杂的断言中可能更具可读性,但在简单的断言中可读性较差。在适当的时候使用它们是可以的。(注:Perl程序员)

它也可能产生更好的故障诊断,因为你给了NUnit更多关于你的意图的信息。它不是"这个匹配那个",而是"这是真的",所以它可以产生一个更精确和精心设计的断言。尽管NUnit也可能足够聪明,可以看到您正在针对Is.True进行测试,并执行测试。没关系的。