如何对异常进行单元测试?

发布于 2024-09-11 01:50:07 字数 354 浏览 3 评论 0原文

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

public String getServerName() {
    try {

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

有人有好的想法吗?

As you know, exception is thrown at the condition of abnormal scenarios. So how to analog these exceptions? I feel it is challenge. For such code snippets:

public String getServerName() {
    try {

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

Does anybody have good ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

雪化雨蝶 2024-09-18 01:50:07

您可以告诉 junit 正确的行为是获取异常。

在 JUnit 4 中,它是这样的:

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

You can tell junit that the correct behavior is to get an exception.

In JUnit 4, it goes something like:

@Test(expected = MyExceptionClass.class) 
public void functionUnderTest() {
    …
}
灯下孤影 2024-09-18 01:50:07

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

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

那么答案是什么呢?

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

  2. 在其他情况下,将生成异常的低级代码分离出来并使其成为一个单独的类可能是明智的。然后,要测试更高级别代码对异常的处理,您可以将该类替换为将引发所需异常的模拟类。

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

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 的实例使用“模拟”类的实例而不是普通类的实例。 (最后一点可以使用模拟框架来完成,通过公开 local 属性的 setter 或使用反射 API。)

显然,您需要修改您的代码使其可像这样进行测试。而且您可以做的事情是有限的...例如,您现在无法创建单元测试来使真正的 LocalDetails.getLocalHost() 方法引发异常。你需要根据具体情况判断是否值得这样做;即,单元测试的好处是否超过了以这种方式使类可测试的工作(以及额外的代码复杂性)。 (事实上​​,其底部有一个static 方法,这是问题的很大一部分。)


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

Other answers have addressed the general problem of how to write a unit test that checks that an exception is thrown. But I think your question is really asking about how to get the code to throw the exception in the first place.

Take your code as an example. It would be very hard to cause your getServerName() to internally throw an exception in the context of a simple unit test. The problem is that in order for the exception to happen, the code (typically) needs to be run on a machine whose networking is broken. Arranging for that to happen in a unit test is probably impossible ... you'd need to deliberately misconfigure the machine before running the test.

So what is the answer?

  1. In some cases, the simple answer is just to take the pragmatic decision and not go for total test coverage. Your method is a good example. It should be clear from code inspection what the method actually does. Testing it is not going to prove anything (except see below **). All you are doing is improve your test counts and test coverage numbers, neither of which should be project goals.

  2. In other cases, it may be sensible to separate out the low-level code where the exception is being generated and make it a separate class. Then, to test the higher level code's handling of the exception, you can replace the class with a mock class that will throw the desired exceptions.

Here is your example given this "treatment". (This is a bit contrived ... )

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 "";
        }
    }
}

Now to unit test this, you create a "mock" implementation of the ILocalDetails interface whose getLocalHost() method throws the exception you want under the appropriate conditions. Then you create a unit text for SomeClass.getServerName(), arranging that the instance of SomeClass uses an instance of your "mock" class instead of the normal one. (The last bit could be done using a mocking framework, by exposing a setter for the local attribute or by using the reflection APIs.)

Obviously, you would need to modify your code to make it testable like this. And there are limits to what you can do ... for example, you now cannot create a unit test to make the real LocalDetails.getLocalHost() method to throw an exception. You need to make a case-by-case judgement as to whether it is worth the effort of doing this; i.e. does the benefit of the unit test outweigh the work (and extra code complexity) of making the class testable in this way. (The fact that there is a static method at the bottom of this is a large part of the problem.)


** There is a hypothetical point to this kind of testing. In your example, the fact that the original code catches an exception and returns an empty string could be a bug ... depending on how the method's API is specified ... and a hypothetical unit test would pick it up. However, in this case, the bug is so blatant that you would spot it while writing the unit test! And assuming that you fix bugs as you find them, the unit test becomes somewhat redundant. (You wouldn't expect someone to re-instate this particular bug ...)

我喜欢麦丽素 2024-09-18 01:50:07

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

测试异常本身很容易

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"));
    }
}

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

现在,对于您的具体示例,当您无法与对象交互时,测试您在方法中创建的内容(通过 new 或静态方式)是很棘手的。您通常需要封装该特定生成器,然后使用一些模拟来覆盖该行为以生成您期望的异常。

Okay there are a few possible answers here.

Testing for an exception itself is easy

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"));
    }
}

Alternately, you can use the Exception Annotation to note that you expect an exception to come out.

Now, as to you specific example, Testing that something you are creating inside your method, either via new or statically as you did, when you have no way to interact with the object is tricky. You normally need to encapsulate that particular generator and then use some mocking to be able to override the behavior to generate the exception you expect.

扛刀软妹 2024-09-18 01:50:07

由于这个问题位于社区 wiki 中,为了完整性,我将添加一个新问题:
您可以在 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"))
    }
}

因为如果您忘记失败,测试可能会导致漏报。

Since this question is in community wiki I'll add a new one for completeness:
You can use ExpectedException in JUnit 4

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

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

The ExpectedException makes the thrown exception available to all test methods.

Is is also possible to test for a specific error message:

thrown.expectMessage("Error string");

or use matchers

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

This is shorter and more convenient than

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

because if you forget the fail, the test can result in a false negative.

木槿暧夏七纪年 2024-09-18 01:50:07

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

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

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

Many unit testing frameworks allow your tests to expect exceptions as part of the test. JUnit, for example, allows for this.

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

    int i = intArray[20]; // Should throw IndexOutOfBoundsException
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文