今天我与一些同事就单元测试进行了讨论。一个有争议的方面是你是否需要对一个函数的所有合同进行单元测试。
示例:
public void foo(@NonNull Type1 arg1, @NonNull Type2 arg2) {
requireNonNull(arg1);
requireNonNull(arg2);
// ...
}
虽然我同意在实现中检查@NonNull
合同(Objects.requireNonNull
语句)是个好主意,但我不同意你应该为它编写单元测试。
以下是一个例子:
@Test(expected = NullpointerException.class)
public void fooMustThrowIfArg1IsNull() {
Type2 dummy = ...;
foo(null, dummy);
}
到目前为止,我试图更多地关注您编写的代码的行为。据我了解TDD和BDD样式,你总是从测试开始,然后实现足以通过测试,依此类推。但是,我从未见过如上所述的测试开始的TDD或BDD从业者。
我同意你应该让你的单元测试尽可能简单,以隔离不同的方面(尽可能少的断言)。但是我担心像fooMustThrowIfArg1IsNull
这样的测试比维护成本更高,即使你忽略了编写它的时间。我看到的唯一例外是关键API的测试(例如,Java标准库实现或Google Guava)。
由于主题是高度自以为是,简单的是或否答案是困难的。通过链接到专家选项以及对该主题的明确建议来支持它将是有帮助的。可能的来源:
(我发现搜索引擎并没有真正帮助,不幸的是我还没有阅读任何有关测试的书籍。)
答案 0 :(得分:3)
嗯,没有明确的答案。但是,检查代码的合同似乎是个好主意。如果没有样板代码......
我在Google Guava's来源中找到了一种有趣的方法。例如,此测试是SplitterTest.java
:
@GwtIncompatible("NullPointerTester")
public void testNullPointers() {
NullPointerTester tester = new NullPointerTester();
tester.testAllPublicStaticMethods(Splitter.class);
tester.testAllPublicInstanceMethods(Splitter.on(','));
tester.testAllPublicInstanceMethods(Splitter.on(',').trimResults());
}
所以,至少谷歌开发人员测试他们的空合约。 (对NullPointerTester
进行点击会导致335次点击。)
NullPointerTester
假设传入@Nullable
时未标记为null
的构造函数和方法应立即失败。它们将失败定义为抛出NullPointerException
或{{ 1}}。
我认为这是一个非常好的技巧。它们可以获得测试 fail fast 语义的所有好处,但避免编写大量无聊的机械测试。
那么,你应该编写单元测试以检查失败快速 UnsupportedOperationException
合同吗?也许最好让 reflection 为你完成这项工作。 : - )
答案 1 :(得分:2)
这取决于你的合同。如果你的合同说明:'那个方法应该在null参数上抛出异常'然后你应该测试它,因为这是预期的行为。但是如果你的合同说:'该方法适用于非空参数,并且它的行为未定义为空参数'然后你不会测试它,因为它超出了范围。在后一种情况下,添加所有requireNonNull前置条件只是为了强制执行失败快速策略并使调试更容易