【问题标题】:Bad form for JUnit test to throw exception?JUnit 测试抛出异常的错误形式?
【发布时间】:2010-12-22 14:57:44
【问题描述】:

我对 JUnit 还是很陌生,我真的不知道异常和异常处理的最佳实践是什么。

例如,假设我正在为 IPAddress 类编写测试。它有一个构造函数 IPAddress(String addr),如果 addr 为 null,它将抛出 InvalidIPAddressException。据我通过谷歌搜索得知,null 参数的测试将如下所示。

@Test
public void testNullParameter()
{
    try
    {
        IPAddress addr = new IPAddress(null);
        assertTrue(addr.getOctets() == null);
    }
    catch(InvalidIPAddressException e)
    {
        return;
    }

    fail("InvalidIPAddressException not thrown.");
}

在这种情况下,try/catch 是有意义的,因为我知道异常即将到来。

但现在如果我想编写 testValidIPAddress(),有几种方法可以做到:

方式#1:

@Test
public void testValidIPAddress() throws InvalidIPAddressException
{
    IPAddress addr = new IPAddress("127.0.0.1");
    byte[] octets = addr.getOctets();

    assertTrue(octets[0] == 127);
    assertTrue(octets[1] == 0);
    assertTrue(octets[2] == 0);
    assertTrue(octets[3] == 1);
}

方式#2:

@Test
public void testValidIPAddress()
{
    try
    {
        IPAddress addr = new IPAddress("127.0.0.1");
        byte[] octets = addr.getOctets();

        assertTrue(octets[0] == 127);
        assertTrue(octets[1] == 0);
        assertTrue(octets[2] == 0);
        assertTrue(octets[3] == 1);
    }
    catch (InvalidIPAddressException e)
    {
        fail("InvalidIPAddressException: " + e.getMessage());
    }
}

标准做法是向 JUnit 抛出意外异常还是自己处理?

感谢您的帮助。

【问题讨论】:

    标签: java unit-testing testing junit


    【解决方案1】:

    对于空测试,您可以简单地这样做:

    public void testNullParameter() {
        try {
                IPAddress addr = new IPAddress(null);
                fail("InvalidIPAddressException not thrown.");
        }
        catch(InvalidIPAddressException e) { }
    }
    

    如果异常有消息,您还可以根据需要在 catch 中检查该消息。例如

    String actual = e.getMessage();
    assertEquals("Null is not a valid IP Address", actual);
    

    对于有效测试,您不需要捕获异常。如果抛出异常但未被捕获,测试将自动失败。因此,第 1 种方式将是您所需要的,因为它会失败,并且无论如何您都可以使用堆栈跟踪以供您查看。

    【讨论】:

      【解决方案2】:

      如果我理解你的问题,答案是 - 个人喜好。

      我个人在测试中抛出我的异常。在我看来,断言失败的测试等同于未捕获异常导致的测试失败。两者都显示了需要修复的内容。

      在测试中要记住的重要一点是代码覆盖率。

      【讨论】:

        【解决方案3】:

        对于我不希望出现异常的测试,我不会费心去捕捉它。我让 JUnit 捕获异常(它可靠地执行此操作)并且除了声明 throws 原因(如果需要)之外根本不满足它。

        我注意到了。您的 first 示例,您没有使用 @expected 注释即。

        @Test (expected=IndexOutOfBoundsException.class) public void elementAt() {
            int[] intArray = new int[10];
        
            int i = intArray[20]; // Should throw IndexOutOfBoundsException
          }
        

        我将它用于我正在测试的所有测试以抛出异常。它比我在 Junit3 中使用的等效捕获/失败模式更简洁。

        【讨论】:

          【解决方案4】:

          实际上,旧式的异常测试是在抛出异常的代码周围包裹一个try块,然后在try块的末尾添加fail()语句。像这样的:

          public void testNullParameter() {
              try {
                  IPAddress addr = new IPAddress(null);
                  fail("InvalidIPAddressException not thrown.");
              } catch(InvalidIPAddressException e) {
                  assertNotNull(e.getMessage());
              }
          }
          

          这和你写的没什么不同,但是:

          1. 你的assertTrue(addr.getOctets() == null);没用。
          2. IMO 的意图和语法更清晰,因此更易于阅读。

          不过,这有点难看。但这就是 JUnit 4 的用武之地,因为异常测试是 JUnit 4 中最大的改进之一。使用 JUnit 4,您现在可以像这样编写测试:

          @Test (expected=InvalidIPAddressException.class) 
          public void testNullParameter() throws InvalidIPAddressException {
              IPAddress addr = new IPAddress(null);
          }
          

          很好,不是吗?

          现在,关于真正的问题,如果我不希望抛出异常,我肯定会选择方法 #1(因为它不那么冗长)并让 JUnit 处理异常并按预期使测试失败。

          【讨论】:

          • 我实际上为 testNullParameter() 尝试了 @Test(expected=InvalidIPAddressException.class) 并且测试在 Eclipse 中返回了错误。你遇到过这种情况吗?
          • 嗯,我刚刚尝试使用 JUnit 4 和 Eclipse,测试运行良好。但是,我忘记了答案中的 throws 子句,如果 InvalidIPAddressException 是已检查异常,则该子句是必需的。我已经相应地修改了我的答案。这有帮助吗?
          • 没有。不过,我可能会对此发表一个不同的问题。感谢您的帮助。
          • @Test(expected=...) 形式实际上可能比显式的 try/catch 更糟糕,例如,如果测试的“设置”部分可以抛出预期的异常。即使执行的方法停止抛出正确的异常,您也会丢失错误定位,并且您的测试可以保持绿色。
          • try-catch 方法将允许您测试抛出的异常的特征,如果在上下文中这是一件有趣的事情。
          【解决方案5】:

          一般来说,方法 #1 是要走的路,没有理由因为错误而指出失败 - 无论哪种方式,测试基本上都失败了。

          如果您需要一个关于哪里出了问题的好消息,那么唯一的方法#2 是有意义的,并且只是一个例外不会给您。然后捕捉和失败可以更好地宣布失败的原因。

          【讨论】:

            【解决方案6】:

            IMO 最好处理异常并从测试中显示适当的消息,而不是从测试中抛出它。

            【讨论】:

            • 此解决方案为测试添加了更多样板,几乎没有任何好处。通过将有问题的测试标记为错误,让 JUnit 处理意外异常;如果测试的成功结果取决于测试主体引发特定异常,请使用 Test 注释的“预期”属性,或使用 ExpectedException JUnit 规则。
            • 测试应该设计成可以运行多次。测试的最大优势在于它们是自动化的例如:每次您将代码签入源代码控制时,测试都可以运行。因此,很少使用为人类输出有意义的文本的测试(因为几乎没有人为干预)因此原则上有一个测试只测试一个方面,如果它失败了,你必须能够毫无疑问地告诉失败的原因。
            【解决方案7】:

            从 JUnit 4.7 开始,您可以使用ExpectedException 规则并且您应该使用它。该规则使您可以准确定义应在测试代码中引发异常的调用方法。此外,您可以轻松地将字符串与异常的错误消息进行匹配。在您的情况下,代码如下所示:

                @Rule
                public ExpectedException expectedException = ExpectedException.none();
            
                @Test
                public void test() {
                    //working code here...
                    expectedException.expect(InvalidIPAddressException.class);
                    IPAddress addr = new IPAddress(null);
                }
            

            更新:在他的书中Practical Unit Testing with JUnit and MockitoTomek Kaczanowski 反对使用 ExpectedException,因为规则“破坏了单元测试的排列/动作/断言 [...] 流程”(他建议改用Catch Exception Library)。虽然我能理解他的论点,但我认为如果您不想引入另一个 3rd-party 库,则使用该规则很好(无论如何,使用该规则总比“手动”捕获异常要好)。

            【讨论】:

            • 这条规则断言某种类型的异常被抛出。除此之外,通过将字符串与异常的错误消息进行匹配是一种完全不同的测试类型,并且确实属于 UI 或客户端界面测试的领域。对于异常消息的 UI/客户端接口相关测试,您可能还需要考虑字符串可以本地化,在这种情况下,您将需要 UI 区域设置信息等。此外,可以显式实例化异常及其消息(s) 直接测试。
            • mrjmh 是正确的,匹配消息可用于 UI 测试(仅供参考,ExpectedException 在当前 4.11 版本中不提供expectLocalizedMessage 或类似方法)。除了 UI 测试之外,匹配异常消息的问题是它经常导致过度指定的测试。
            【解决方案8】:

            Reg:异常测试
            我同意“Pascal Thivent”,即使用@Test (expected=InvalidIPAddressException.class)


            注册:测试testValidIPAddress

            IPAddress addr = new IPAddress("127.0.0.1");
            byte[] octets = addr.getOctets();
            

            我会写一个类似的测试

            class IPAddressTests
            {
            
                [Test]
                public void getOctets_ForAValidIPAddress_ShouldReturnCorrectOctect()
                {
                     // Test code here
                }
            
            }
            

            关键是当 testinput 是 VALID ipAddress
            测试必须针对类上的公共方法/功能,断言它们作为例外工作

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-02-08
              • 2013-01-21
              • 2012-08-07
              • 1970-01-01
              • 2018-06-27
              相关资源
              最近更新 更多