将NullPointerException视为单元测试失败:这是一个好的实践吗?

7

假设您有一个包含以下行的单元测试:

assertNotNull(someVal);
assertNotEmpty(someVal);

这显然检查了someVal不为null并且被填充了一些东西。

问题是,第一行是否必要并且是否对测试有所帮助?我们不应该只有第二行吗?如果它为空,它将抛出一个空指针异常,这仍然表示单元测试失败(但不是断言失败)。

在这种简单情况下,最佳实践是什么?

4个回答

5

在测试执行过程中,出现NPE时不应该视为测试失败。相反,您应该断言其不为空并提供错误消息:

assertNotNull(someVal, "应提供一些具有描述性的消息,说明为何值不应为null");


为什么?这里没有解释。 - undefined

4

这要看情况。假设您正在测试myFunc函数。如果myFunc在非异常情况下返回null,则检查结果既不为空也是正确的值,这样可以使您的测试更清晰。

但如果null是一个异常情况,那么检查它就像在测试中捕获RuntimeException一样。这只会让您的测试变得混乱并且模糊了您的意图。


我不同意,如果方法返回null而不是抛出异常,则返回必须是明确的。因此,该方法被设计为返回null,因此应执行null检查。正如Matthew所说,在测试中发生NPE并不是一件好事,因为看到失败的人不知道是代码还是测试中的错误。 - John B
1
@JohnB 明确,但可能不是有意为之。或者这是一个较低级别的类返回 null 的结果(return componentObject.getFunctionResult())。此外,请考虑以下内容。这种推理意味着每当您在返回对象的函数上编写测试时,都应该使用 assertNotNull()。您这样做了吗? - sharakan
实际上是的,因为我做TDD,所以首先要做的事情就是返回一些非空值。我知道如果你不做TDD,这可能不那么明显,但仍然是一个好习惯。话虽如此,在检查返回对象中的每个内容时,我并不总是先检查是否为空。嗯,这是好习惯还是懒惰呢?我有点犹豫。 - John B
@JohnB 关于您对返回对象进行检查的观点很有意思。我认为这是一个值得注意的点,但我个人可能会这样做;)。然而,回到TDD中首先检查非空对象的问题上,您是否还会在被测试函数周围添加try/catch块来捕获RuntimeException,并在catch块中调用fail()函数?如果没有的话,为什么这两者不同呢? - sharakan
由于异常堆栈跟踪中的原因代码行。如果异常是在测试方法中抛出的,那么您就知道错误在测试方法中。如果异常是从测试中的某行代码抛出的,则不太明显哪个有错误(测试或测试方法)。 - John B

3

在JUnit中,故障和错误有所不同。故障是预期的事情(使用assertXXX()fail()进行显式测试),而错误通常是异常情况。请参见What's the difference between failure and error in JUnit?

如果您调用

assertNotNull(someVal);

如果您说这个值不应该为空,并且您正在特别测试它。如果让NullPointerException发生,那么解释错误的人将不知道被测试的代码是否正确,或者您只是没有考虑所有情况。

这是意图的问题。正如其他人指出的那样,添加说明消息是一个好主意。


2

我不太喜欢使用assertNot(),更普遍地说,不喜欢测试可能发生在SUT上的意外情况,更不要在每个测试中除了正常断言之外再测试它们。

我认为你只应该测试对象的正常状态。如果在某些情况下someVal为空是正常的,则创建一个专用的测试方法来检查在该场景下它确实为空。为你的测试选择一个表达意图的名称,如someValShouldBeNullWhen...()。对于空值或任何其他值也可以这样做。

通过查看测试异常消息,您可以看到意外情况发生在SUT上,而不是试图预测每个意外情况并将其塞入assertNots中。


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