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

Jos*_*eph 33 java junit

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

public String getServerName() {
    try {

        InetAddress addr = InetAddress.getLocalHost();
        String hostname = addr.getHostName();
        return hostname;
    }
    catch (Exception e) {
        e.printStackTrace();
        return "";
    }
}
Run Code Online (Sandbox Code Playgroud)

有人有好主意吗?

Uri*_*Uri 67

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

在JUnit 4中,它类似于:

@Test(expected = MyExceptionClass.class) 
public void functionUnderTest() {
    …
}
Run Code Online (Sandbox Code Playgroud)


Ste*_*n C 34

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

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

所以答案是什么?

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

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

这是"治疗"的例子.(这有点人为......)

public interface ILocalDetails {
    InetAddress getLocalHost() throws UnknownHostException;
    ...
}
Run Code Online (Sandbox Code Playgroud)
public class LocalDetails implements ILocalDetails {
    public InetAddress getLocalHost() throws UnknownHostException {
        return InetAddress.getLocalHost();
    }
}
Run Code Online (Sandbox Code Playgroud)
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 "";
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在对单元测试,你创建一个ILocalDetails接口的"模拟"实现,其getLocalHost()方法在适当的条件下抛出你想要的异常.然后你创建一个单元文本SomeClass.getServerName(),安排实例SomeClass使用你的"模拟"类的实例而不是正常的实例.(最后位可以使用一个模拟框架来完成,通过暴露一setterlocal属性或通过使用反射的API.)

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


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


Mat*_*att 14

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

对异常进行测试很容易

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"));
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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


Ger*_*ger 8

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

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

@Test
public void TestForException(){
    thrown.expect(SomeException.class);
    DoSomething();
}
Run Code Online (Sandbox Code Playgroud)

ExpectedException使得抛出的异常提供给所有的测试方法.

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

thrown.expectMessage("Error string");
Run Code Online (Sandbox Code Playgroud)

或使用匹配器

thrown.expectMessage(startsWith("Specific start"));
Run Code Online (Sandbox Code Playgroud)

这比更短,更方便

public void TestForException(){
    try{
        DoSomething();
        Fail();
    }catch(Exception e) {
      Assert.That(e.msg, Is("Bad thing happened"))
    }
}
Run Code Online (Sandbox Code Playgroud)

因为如果你忘了失败,测试可能会导致误报.


End*_*ssa 6

许多单元测试框架允许您的测试将异常作为测试的一部分.例如,JUnit 允许这样做.

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

    int i = intArray[20]; // Should throw IndexOutOfBoundsException
}
Run Code Online (Sandbox Code Playgroud)