如何在测试用例中避免“过度测试”?(C#/nUnit)

7

我目前正在处理一些测试用例,而且我经常发现在每个用例中都有多个断言。例如(为了简洁起见,已经过度简化并删除了注释):

[Test]
public void TestNamePropertyCorrectlySetOnInstantiation()
{
  MyClass myInstance = new MyClass("Test name");
  Assert.AreEqual("Test Name", myInstance.Name);
}

原则上看起来还不错,但测试的目的是验证当使用给定名称实例化类时,Name属性是否设置正确,但如果在实例化之前出现任何问题,它将失败,甚至在进行断言之前就失败了。

我将其重构为:

[Test]
public void TestNamePropertyCorrectlySetOnInstantiation()
{
  MyClass myInstance;
  string namePropertyValue;

  Assert.DoesNotThrow(() => myInstance = new MyClass("Test name"));
  Assert.DoesNotThrow(() => namePropertyValue = myInstance.Name);
  Assert.AreEqual("Test Name", namePropertyValue);
}

当然,现在我正在测试三件事情; 在这个测试中,我不感兴趣的是测试MyClass实例是否成功实例化,或者Name属性是否被成功读取,这些在另一个案例中测试。但是如果前两个失败,如何测试最后一个断言而不先断言其他两个呢?

4个回答

12

还有其他的测试可以检查如果以无效的方式进行初始化,则会引发异常。在我看来,第一种形式在这个时候很好。

个人而言,我会避免陷入“每个测试一个断言”的教条中。尽量测试代码中的一个逻辑路径,使其尽可能细粒度。


没错,这并不是要坚持“每个测试一个断言”的原则,而是在代码实际运行正常时,不希望看到测试结果出现红色的错误提示。 - Flynn1179
@Flynn1179:你为什么会得到一个红色的斑点?我不明白你的意思。 - Jon Skeet
抱歉,我忘了提到; 我使用ReSharper运行nUnit测试时,当测试失败时,它会显示为测试列表中的红色块。在实际断言之前,有很多测试 '失败' 是由于一个错误导致的。 - Flynn1179
1
@Flynn1179:我的态度是,如果你有任何测试失败,那么很可能会导致其他测试也失败。如果构造函数总是抛出异常,那么所有需要实例的测试都将不可避免地失败。这就是生活。重要的是应该很容易发现故障并修复它。 - Jon Skeet

1

我真的不明白你所说的过度测试是什么意思。在我看来,过度测试就像试图测试私有方法一样。

我认为我的测试是代码文档。因此,如果语句中有多个assert,那么很有可能我会将被测试的方法重构为几个较小的方法,或者有时我会将我的测试方法拆分为几个不同的测试方法。遵循每个测试一个assert的规则允许您拥有明智的测试方法名称,从而形成代码的文档。我遵循的测试方法命名约定是methodName_scenario_expectation(来自Roy Osherove的单元测试艺术)。因此,也可以考虑代码文档。如果您认为有一个assert会帮助您/其他开发人员更好地理解代码(除了验证期望),那么请编写该assert。但是,请再次强调,始终确保您具有适当的测试方法名称。


通过“过度测试”,我指的是当测试用例“测试”除其预期功能以外的其他功能,这是设置所需测试的必然结果。 - Flynn1179

0

Grzenio说得对。如果抛出异常,第一个、最简单的示例测试将失败-无需显式测试。

但是,您应该测试当传递无效数据时是否会引发异常。

[Test]
public void TestInvalidNameThrowsException {
  MyClass myInstance;
  string namePropertyValue;
  Assert.Throws(() => myInstance = new MyClass(null));
  Assert.Throws(() => myInstance = new MyClass("INVALID_NAME_125356232356"));
}

(例如,我一点也不懂C#。但是你应该能理解这个想法。)

0
在您的特定示例中,如果某些内容是测试执行的一部分,您不需要断言它不会抛出异常。这个方面已经在您的第一个测试中得到了验证,而且更易于阅读。如果构造函数或属性getter引发异常,则NUnit将以适当的错误消息使测试失败。说实话,我不确定Assert.DoesNotThrow()背后的想法是什么,因为如果您省略它,您几乎可以得到相同的结果 - 但绝不能将其用作正常测试执行的一部分。将异常作为语言的一部分的整个重点在于,您不需要在每行代码之后检查错误。

是的,我认为这可能不是最好的例子...但总的来说,有些测试需要一些“设置”,这些设置特定于该测试,但与您正在测试的功能无直接关系,这是非常常见的。我正在看Assert.Inconclusive,也许可以将“设置”包装在try/catch中,如果失败,则断言无法确定,但这会使我的代码更难以阅读。 - Flynn1179

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接