2

总的来说,我真的是 JUnit 和单元测试的新手,我正在努力寻找正确的方法。处理意外异常的更好方法是什么,为什么?

方法一:

  1. 首先捕获预期的,通过一条消息使测试失败
  2. 在最后一个 catch 块中,捕获一个一般异常并通过一些“发生意外异常”消息使测试失败

方法B:

  1. 仅捕获预期的,失败并显示消息
  2. 用标记测试方法throws Exception并让任何意想不到的“冒泡”完全退出测试

更令人困惑的是,通过说“意外异常”,我的意思是以下任何一种:

  1. 被测试的方法声称抛出的异常,但在这种情况下。例如,我的方法抛出 IllegalArgumentException 和 NullPointerException,但在这个 try 块中,我希望它抛出 IllegalArgument;NullPointer 被认为是意外的。赶上失败还是冒泡?
  2. 我真的无法预料的一般例外

我知道这个问题有点令人困惑,我自己也迷失了,但希望有人能给我任何暗示。不会因为投反对票而责怪你,它仍然值得冒险:)

4

5 回答 5

3

使用 JUnit 的测试方法,基本上,如果方法成功结束,JUnit 认为测试通过。所以你应该做的是捕捉所有应该到达的异常。在这种情况下,您可以执行以下操作:

@Test
public myMethodToTest()
{
    try {
        /* ... */
        /* error should trigger before the following statement : */
        fail("Reason");
    } catch(GoodException e) { } 
   // no need to do anything since you expected that error
   // You can let the test ends without further statement 
}

还有另一种写法:

@Test (expected=IndexOutOfBoundsException.class)
public void elementAt()
{
    int[] intArray = new int[10];

    int i = intArray[20]; // Should throw IndexOutOfBoundsException
}

如果您仍然想要,您仍然可以捕获不应触发的异常,并执行以下操作:

@Test
public myMethodToTest()
{
    try {
        /* ... */
    } catch(BadException e) {
        fail("Reason");
    }

}
于 2013-05-12T14:08:48.997 回答
2

您可以使用此语法查看 SO 问题(在 Java 中断言异常,如何?

@Test(expected=NumberFormatException.class);

以确保可能引发预期的异常。

某些方法可能会合法地抛出多个异常。例如,如果您正在求解一个二次方程,您可以期望除以零和负平方根。您应该通过编写两个单独的单元测试来测试其中的每一个,每个单元测试都被设计为失败,因此测试是否引发了异常。

例子:

Pair roots = solveQuadratic(double a, double b, double c) {
    //... 
}

其中 Pair 是预期的解决方案对。

这是我们希望通过的测试。

@Test
public void testQuadratic() {
    Pair roots = solveQuadratic(1.0, -5.0, 6.0);
    Assert.assertEqual(3.0, roots.largest, 0.000001); 
    Assert.assertEqual(2.0, roots.smallest, 0.000001); 
}

这是我们预计会失败的一个:

@Test
public void testQuadraticDivideByZero() {
    try {
        Pair roots = solveQuadratic(0.0, -5.0, 6.0);
        Assert.fail("should have failed with ArithmeticException");
    } catch (ArithmeticException e) {
    }
}

在这种情况下,您可能想要编写代码以便捕获a=0.0并抛出带有消息的 IllegalArgumentException。

一般来说,您应该考虑所有可能出错的事情,并诱捕它们并测试它们。因为有一天有人传递a=0.0给这个方法并且需要一个明确的错误信息。

于 2013-05-12T14:25:27.630 回答
2

我有感觉,到目前为止,没有一个答案能真正触及问题的重点。OP 明确要求处理意外异常。我在这个话题上的两分钱是:

这取决于您想要达到的详细程度:

通常,您应该努力进行简短的测试,以查明代码的一个方面。理想情况下,只需要调用少数几个方法,其中只有一两个可能会引发意外异常。在这种情况下,为所有检查的异常添加throws子句应该足以在测试失败时分析问题。我更喜欢这个解决方案,因为它更容易编写、更短且更全面

例子:

@Test
public void testPrivateMethod() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

    //...

    Method method = MyClass.class.getMethod("privateMethod");
    method.setAccessible(true);
    boolean result = method.invoke(myInstance);
    assertTrue(result);
}

如果测试代码需要更复杂,可能有多个方法负责引发异常。可能,其中一些甚至会抛出相同类型的异常。在这种情况下,当您的测试用例失败时,try-catch 块可能有助于定位问题。但是,这会产生更多代码,并且可能会使测试的可读性降低

例子:

@Test
public void testPrivateMethod() {

    //...

    Method method;
    try {
        Method method = MyClass.class.getMethod("privateMethod");
        method.setAccessible(true);
        boolean result = method.invoke(myInstance);
        assertTrue(result);
    } catch (NoSuchMethodException | SecurityException e) {
        fail("Could not access method 'privateMethod'.");
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
        fail("Call to 'privateMethod' raised an exception.")
    }
}

我希望我能正确理解问题的意图。

于 2017-06-13T08:45:53.517 回答
1

对方法进行单元测试的想法是确保它是一种可靠的方法。可靠性意味着对于给定的输入,输出始终是预期的。因此,这取决于方法是否存在异常。如果我们认为发生了意外的异常并且方法应该对此不做任何事情,那么您可以将测试用例标记为 PASSED。但是,如果期望该方法处理任何异常情况并且如果它不能处理,则将测试用例标记为 FAILED。

于 2013-05-12T14:04:24.760 回答
1

拥有测试用例的想法是通过向其提供所有边界条件的输入来避免所有意外异常

如果该方法抛出一些Exceptions(该方法有throws子句并试图用一些检查的异常通知调用者),则传递使您的测试用例抛出该异常的输入,并将catch其放在您的测试用例中。这使得测试用例之一PASS

如果该方法抛出意外异常,请修复该方法。一个方法应该足够稳定不会抛出意外的异常,比如NullPointerException并且应该被修复。

因此,要回答您的问题,处理意外异常的更好方法是通过增强您的测试用例来修复它来找到它们。

于 2013-05-12T14:24:18.430 回答