3

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

[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:我对单元测试比较陌生...

4

2 回答 2

6

最简洁的答案是不。你应该让你的测试尽可能简单。每个测试应该只测试一件事。

有一个用于单元测试的Arrange-Act-Assert (AAA)模式。这表明您应该在测试方法的开头做一些准备(安排),然后执行此测试检查并在方法结束时做出一些断言。

此外,我可能会建议您阅读有关单元测试的FIRST模式。

升级版:

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

但是您应该考虑到您对GRASP的测试也应该是低耦合/高内聚的,因此,正如@Schwern 在他的回答中提到的那样,如果不同的测试将结束,您不应该仅仅为了最小化断言而编写单独的测试向上测试相同的逻辑事物。开发人员始终可以自行决定在每种特定情况下哪种方式是正确的。

于 2013-01-13T20:01:42.317 回答
5

注意:我不是 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 的输入。

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

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

当您颠倒他们的顺序时,检查是否有Hotkeys.IsRegisteredGlobal("Alt+P+Ctrl")可能成为真的?Hotkeys.IsRegisteredGlobal("ctrl+alt+p")是的。但在隔离、速度和便利性之间总是需要权衡取舍。如果您怀疑其中一个可能会干扰另一个,您应该隔离它们。我想说,如果你想检查两者之间没有耦合,那么你应该在自己的测试中明确地做到这一点,而不是让每个测试都背负这个负担。

是的,运行代码并对其执行多个断言很好,但请始终记住,这是良好代码和良好测试之间的平衡行为。通常测试会获胜,但不要对此感到愚蠢。

为了紧凑、清晰和可能更好的故障诊断, 我将其切换到IsTrue而不是通用的。意味着你直到最后都不知道你在测试什么。你必须阅读整行,看看条件是什么,然后根据你真正测试的内容再次阅读整行。 提前告诉你。 在复杂的断言中可能更具可读性,但在简单的断言中可读性较差。可以酌情使用它们。(注:Perl 程序员ThatAssert.That( thing, condition )Assert.IsTrueAssert.That

它还可能产生更好的故障诊断,因为您向 NUnit 提供了有关您的意图的更多信息。不是“这与那个匹配”而是“这是真的”,因此它可以产生更准确和精心制作的断言。尽管 NUnit 也可能足够聪明,可以看到您正在测试Is.True并执行此操作。它不疼。

于 2013-01-13T20:13:29.667 回答