我在某处读到每个测试必须只测试一件事。但是,在良好实践手册中允许对类似行为进行分组吗?我目前正在编写一些测试(C#with NUnit),而bellow就是我面临的一个例子:
[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:我对单元测试比较陌生......
答案 0 :(得分:6)
简短的回答是否定的。您应该尽可能简化测试。每项测试只应测试一件事。
单元测试有Arrange-Act-Assert (AAA)模式。其中说明你应该在测试方法的开头做一些准备(安排),然后做一下这个测试的动作,并在方法结束时做一些断言。
另外,我建议您阅读有关单元测试的FIRST模式。
<强> UPD:强>
当你使测试变得复杂时 - 当测试变为“红色”时很难识别出什么是错误的,你知道某些断言失败了,但你必须阅读日志以了解哪一个。此外 - 如果复杂测试中的第一个断言失败,您甚至不知道其余断言是否正常。维护大型单元测试也很困难,你为第一个断言做的一些工作可能会产生影响下一个断言的副作用。
但你应该考虑到你对GRASP的测试也应该是低耦合/高内聚,因此,正如@Schwern在他的回答中所提到的,你不应该只是为了尽量减少分开的测试断言如果不同的测试最终会测试相同的逻辑事物。开发人员始终可以自行决定在每种特定情况下采用哪种方式。
答案 1 :(得分:5)
注意:我不是C#程序员。
一方面,你有“在测试中只做一件事”。另一方面,你有DRY Principle。你被问到要违反哪一个。这取决于你违反这些规则的程度,违规行为带来的好处,以及这些规则存在的原因。
您的分组解决方案并不理想,因为它仍然会重复。如果你这样做了......
[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的输入。
每次测试只做一件事就是为了隔离它们。这使得测试更容易隔离和调试。这并不意味着每次测试都有一个断言。上面的测试仍然只做一件事,但它正在以三种非常非常相似的方式测试它。还行吧。以前,您的断言是由测试名称解释的。现在通过与每个断言相关联的消息来解释它们。失败的原因仍然很明显,我在第一时间。
此外,如果您使用每个方法使用一个断言编写所有测试并且不必要地反复重复相同的代码,则不仅违反DRY,而且重复的代码可能会开始减慢违反第一个F的内容。 / p>
检查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
并且无论如何都要这样做。它没有伤害。