Java单元测试中出现意外异常

Unexpected exceptions in Java Unit tests

我真的是JUnit的新手和一般的单元测试,我很难找到合适的方法。处理意外异常的更好方法是什么?为什么?

方法A:

  • 首先捕获预期的,通过消息测试失败
  • 在最后一个catch块中,捕获一般异常并使测试失败并显示一些"意外异常已发生"消息
  • 方法B:

  • 只捕获预期的,失败的消息
  • throws Exception标记测试方法,让任何意外的"冒泡"完全脱离测试
  • 并且为了增加混乱,通过说"意外异常",我指的是以下任何一种:

  • 被测试的方法声称要抛出的异常,但在这种情况下。例如,我的方法抛出IllegalArgumentException和NullPointerException,但是在这个try块中,我希望它抛出IllegalArgument; NullPointer被认为是意外的。抓住,失败或冒泡?
  • 一般的例外我真的无法预料
  • 我知道这个问题有点令人困惑,我自己也迷失了,但希望有人会给我任何暗示。不会因为downvotes而责怪你,它仍然值得冒险:)


    使用JUnit的测试方法,基本上,如果方法设法无错误地结束,JUnit会认为测试已通过。所以你应该做的是捕获应该到达的所有异常。在这种情况下,这是你可以做的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @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
    }

    还有另一种写作方式:

    1
    2
    3
    4
    5
    6
    7
    @Test (expected=IndexOutOfBoundsException.class)
    public void elementAt()
    {
        int[] intArray = new int[10];

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Test
    public myMethodToTest()
    {
        try {
            /* ... */
        } catch(BadException e) {
            fail("Reason");
        }

    }


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

    1
    @Test(expected=NumberFormatException.class);

    确保可能抛出预期的异常。

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

    例:

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

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

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

    1
    2
    3
    4
    5
    6
    @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);
    }

    这是我们期望失败的一个:

    1
    2
    3
    4
    5
    6
    7
    8
    @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传递给此方法并需要明确的错误消息。


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

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

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

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @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块可能有助于在测试用例失败时定位问题。但是,这会产生更多代码,并可能使测试的可读性降低。

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @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.")
        }
    }

    我希望我的意图是正确的。


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

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

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

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


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