如何对例外进行单元测试?

时间:2010-07-22 02:50:37

标签: java junit

如您所知,在异常情况下会抛出异常。那么如何模拟这些异常呢?我觉得这是挑战。对于此类代码段:

public String getServerName() {
    try {

        InetAddress addr = InetAddress.getLocalHost();
        String hostname = addr.getHostName();
        return hostname;
    }
    catch (Exception e) {
        e.printStackTrace();
        return "";
    }
}

有人有好主意吗?

5 个答案:

答案 0 :(得分:65)

你可以告诉junit正确的行为是获得异常。

在JUnit 4中,它类似于:

@Test(expected = MyExceptionClass.class) 
public void functionUnderTest() {
    …
}

答案 1 :(得分:31)

其他答案解决了如何编写单元测试以检查是否抛出异常的一般问题。但我认为你的问题实际上是询问如何让代码首先抛出异常。

以您的代码为例。在单个单元测试的上下文中使getServerName()内部抛出异常非常困难。问题是,为了使异常发生,代码(通常)需要在网络被破坏的机器上运行。安排在单元测试中发生这种情况可能是不可能的......在运行测试之前,您需要故意错误配置机器。

那么答案是什么?

  1. 在某些情况下,简单的答案只是采取务实的决定,而不是全面的测试覆盖。你的方法就是一个很好的例子。从代码检查中应该清楚该方法实际上做了什么。测试它不会证明什么(除了**下面)。您所做的只是改善测试计数和测试覆盖率,这两者都不应该是项目目标

  2. 在其他情况下,分离出生成异常的低级代码并将其作为单独的类可能是明智的。然后,要测试异常的更高级代码的处理,可以使用将抛出所需异常的mock类替换该类。

  3. 这是“治疗”的例子。 (这有点人为......)

    public interface ILocalDetails {
        InetAddress getLocalHost() throws UnknownHostException;
        ...
    }
    

    public class LocalDetails implements ILocalDetails {
        public InetAddress getLocalHost() throws UnknownHostException {
            return InetAddress.getLocalHost();
        }
    }
    

    public class SomeClass {
        private ILocalDetails local = new LocalDetails();  // or something ...
        ...
        public String getServerName() {
            try {
                InetAddress addr = local.getLocalHost();
                return addr.getHostName();
            }
            catch (Exception e) {
                e.printStackTrace();
                return "";
            }
        }
    }
    

    现在要对其进行单元测试,您可以创建ILocalDetails接口的“模拟”实现,其getLocalHost()方法会在适当的条件下抛出您想要的异常。然后为SomeClass.getServerName()创建单元文本,安排SomeClass的实例使用“mock”类的实例而不是正常类。 (最后一点可以使用模拟框架,通过为setter属性公开local或使用反射API来完成。)

    显然,您需要修改代码以使其像这样可测试。你可以做什么是有限的...例如,你现在无法创建一个单元测试来使真正的LocalDetails.getLocalHost()方法抛出异常。你需要逐案判断是否值得这样做的努力;即,单元测试的好处是否超过了以这种方式使类可测试的工作(以及额外的代码复杂性)。 (在这个问题的底部有一个static方法的事实是问题的很大一部分。)


    ** 是这种测试的假设点。在您的示例中,原始代码捕获异常并返回空字符串的事实可能是一个错误...取决于方法的API如何指定...并且假设单元测试将选择它了。但是,在这种情况下,bug是如此明显,以至于你在编写单元测试时会发现它!假设您在找到错误时修复了错误,则单元测试变得有些多余。 (你不会指望有人重新设置这个特定的错误...)

答案 2 :(得分:13)

好的,这里有一些可能的答案。

检测异常本身很容易

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

@Test
public void TestForException() {
    try {
        doSomething();
        fail();
    } catch (Exception e) {
        assertThat(e.getMessage(), is("Something bad happened"));
    }
}

或者,您可以使用异常注释来指出您希望出现异常。

现在,至于你的具体例子,当你无法与对象交互时,测试你在方法中创建的东西,无论是通过新的还是静态的,都是棘手的。您通常需要封装该特定生成器,然后使用一些模拟来覆盖行为以生成您期望的异常。

答案 3 :(得分:8)

由于这个问题在社区维基中,我会添加一个新的完整性: 您可以在JUnit 4中使用ExpectedException

@Rule
public ExpectedException thrown= ExpectedException.none();

@Test
public void TestForException(){
    thrown.expect(SomeException.class);
    DoSomething();
}

ExpectedException使抛出的异常可用于所有测试方法。

也可以测试特定的错误消息:

thrown.expectMessage("Error string");

或使用匹配器

thrown.expectMessage(startsWith("Specific start"));

这比

更短更方便
public void TestForException(){
    try{
        DoSomething();
        Fail();
    }catch(Exception e) {
      Assert.That(e.msg, Is("Bad thing happened"))
    }
}

因为如果您忘记了失败,那么测试可能会导致误报。

答案 4 :(得分:6)

许多单元测试框架允许您的测试将异常作为测试的一部分。例如,JUnit,allows for this

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

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