在单元测试中拥有多个断言是不好的做法吗?有关系吗?
8 回答
assert
有时我每个测试用例只有一个,但我想更多时候我有几个assert
语句。
我见过@Arkain 逃避的情况,其中一段非常大的代码只有一个单元测试套件,只有几个测试用例,它们都被标记为testCase1
,testCase2
等,每个测试用例都有数百个断言。更好的是,每个条件通常取决于先前执行的副作用。每当构建失败时,总是在这样的单元测试中,需要相当长的时间来确定问题出在哪里。
但另一个极端是您的问题所暗示的:每种可能条件的单独测试用例。根据您要测试的内容,这可能是有道理的,但通常我assert
每个测试用例都有几个 s。
例如,如果您编写了java.lang.Integer
,您可能会遇到如下情况:
public void testValueOf() {
assertEquals(1, Integer.valueOf("1").intValue());
assertEquals(0, Integer.valueOf("0").intValue());
assertEquals(-1, Integer.valueOf("-1").intValue());
assertEquals(Integer.MAX_VALUE, Integer.valueOf("2147483647").intValue());
assertEquals(Integer.MIN_VALUE, Integer.valueOf("-2147483648").intValue());
....
}
public void testValueOfRange() {
assertNumberFormatException("2147483648");
assertNumberFormatException("-2147483649");
...
}
public void testValueOfNotNumbers() {
assertNumberFormatException("");
assertNumberFormatException("notanumber");
...
}
private void assertNumberFormatException(String numstr) {
try {
int number = Integer.valueOf(numstr).intValue();
fail("Expected NumberFormatException for string \"" + numstr +
"\" but instead got the number " + number);
} catch(NumberFormatException e) {
// expected exception
}
}
对于在测试用例中放入多少断言,我可以想到一些简单的规则:
- 不要有多个
assert
依赖于先前执行的副作用。 - 将测试相同功能/特性或其方面的 s组合
assert
在一起——在不需要时不需要多个单元测试用例的开销。 - 上述任何规则都应该被实用性和常识所覆盖。您可能不希望有一千个单元测试用例在每个测试用例中包含一个断言(甚至是多个断言),并且您不希望一个测试用例包含数百个
assert
语句。
不,这不是一个坏习惯。如果您正在测试的方法返回一个类,您应该测试应该设置的不同变量。为此,您不妨使用一个单元测试。
但是,如果您在一个单元测试中测试多个功能,那么当它失败时,您将不清楚是哪些功能导致了问题。记住单元测试是你的朋友,所以让他们帮助你。使其易于查看出了什么问题,以便您可以修复它。
您的单元测试应该是相当细粒度的。通常,断言越少,您的测试就越有可能针对特定功能,而不是在同一测试中混合测试多个功能。这是否意味着所有测试都应该只有一个断言?不,但如果我发现几个断言,我会认为这是一种“测试气味”,可能会在同一个单元测试中测试多个东西。像对待代码气味一样对待这种“气味”,并重构测试以改进它,以便它只测试一个“事物”——即使它需要多个断言。
例如,我现在正在做一个 MVC 项目,我编写的测试之一是正确的视图是由动作呈现的。如果不同的代码路径可能导致不同的视图,则实际上可能有其中的几个。这就是我将其定义为正确视图的方式:结果是正确的类型并具有正确的名称。这需要两个断言,但我只测试一件事。
var result = controller.Action() as ViewResult;
Assert.IsNotNull( result );
Assert.AreEqual( viewName, result.ViewName );
我可能会对模型做类似的事情,但我不会在与检查视图相同的测试中测试模型是否正确,因为这些是代码行为的不同方面。我可以更改预期的模型或视图,并将其放在单独的测试中,只有那些与方法的该特性相关的测试需要更改。
对我来说,在单元测试中有多个断言是很常见的。我通常有一个前提条件的断言,然后是预期的后置条件的断言。
考虑:
assert(list.isEmpty());
FetchValues(list);
assert(list.count == expectedItemCount);
AssertValuesMatch(list,expectedValues);
是的,我可以将两个后置条件分成两个测试,但根据 FetchValues 的成本可能会不必要地减慢整个测试过程。
我不认为这是不好的做法。单元测试可以做任何他们想做的事情:断言、记录到文件、向管理层发送侮辱性 SMS 消息,等等。
可能的问题是,增加的复杂性可能会改变被测程序的行为,但如果你小心的话,这种情况很少发生,而且无论如何都可以被发现。
我绝对应该在测试方法中只使用一个断言!使用许多断言可能是您正在测试不止一件事的代码味道。此外,有人可能会在您的测试中添加新的断言而不是编写另一个断言。当第一个断言失败时,你如何理解你的其他断言是如何完成的?
您可能还会发现这篇文章很有趣:https ://timetocode.wordpress.com/2016/06/01/zen-of-unit-testing/
放入您想要的所有断言。严重地。
我试图断言直到并包括我的测试的具体目标的每一步。
没关系。唯一重要的是您的单元测试将涵盖所有可能的错误。
这是“过度思考”的一个例子。