如何通过单元测试证明这段代码中的错误

发布于 2024-10-22 04:18:25 字数 968 浏览 2 评论 0原文

我们公司有一个习惯,当报告错误时,我们会执行以下步骤:

  1. 编写一个失败的单元测试,清楚地显示错误的存在
  2. 修复错误
  3. 重新运行测试以证明错误已修复
  4. 提交修复和测试避免将来出现回归

现在我遇到了一段带有非常简单错误的遗留代码。情况如下所示:

public final class SomeClass {
    ...
    public void someMethod(Parameter param) {
        try {
            if (param.getFieldValue("fieldName").equals("true")) { // Causes NullPointerException
                ...
            }
        } catch (Exception ex) {
            log.warn("Troubles ...", ex);
        }
    }
}

这里的问题是 fieldName 不是强制性的,因此如果不存在,您将得到 NPE。明显的解决方法是:

if ("true".equals(param.getFieldValue("fieldName"))) {
    ...
}

我的问题是如何编写单元测试以使方法失败。如果我传入一条不包含 fieldName 的消息,它只会记录 NPE,但不会失败......

您可能会想到该方法的作用是什么?我可以测试一下该方法的效果。不幸的是,它与一些远程系统通信,因此这将需要大量的集成测试,对于如此小的和直接的错误来说,这似乎有点过分了。

请注意,在代码中进行任何不直接导致错误的更改(如果不是不可能的话)将非常困难。因此,仅仅为了更容易测试而更改代码可能不是一个选择。这是相当可怕的遗留代码,每个人都不敢碰它。

we have a habit in our company that when a bug is reported we do following steps:

  1. Write a unit test that fails clearly showing the bug exists
  2. Fix the bug
  3. Re-run the test to prove the bug has been fixed
  4. Commit the fix and the test to avoid regressions in the future

Now I came across a piece of legacy code with very easy bug. Situation looks like follows:

public final class SomeClass {
    ...
    public void someMethod(Parameter param) {
        try {
            if (param.getFieldValue("fieldName").equals("true")) { // Causes NullPointerException
                ...
            }
        } catch (Exception ex) {
            log.warn("Troubles ...", ex);
        }
    }
}

The problem here is that fieldName is not mandatory, so if not present, you get NPE. The obvious fix is:

if ("true".equals(param.getFieldValue("fieldName"))) {
    ...
}

My question is how to write a unit test to make the method fail. If I pass in a message which doesn't contain the fieldName it just logs the NPE but it won't fail ...

You may think what the method does? I can test the effect the method has. Unfortunatelly it communicates with some remote system so this will require a huge integration test which seems to be overkill with such a small and straiht-forward bug.

Note that it will be really hard if not impossible to make any changes in the code that are not directly causing the bug. So changing the code just to make it easier to test will probably not be an option. It's quite scary legacy code and everybody is really afraid to touch it.

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

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

发布评论

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

评论(7

夏尔 2024-10-29 04:18:25

您可以做几件事:

  1. 存根记录器并检查是否记录了错误,作为错误是否发生的指示器。如果记录器无法轻松更换,您可以使用 TypeMock 或 Moles。
  2. 将 try 块内的部分重构为自己的方法,并仅调用 try 块内的该方法,并使您的单元测试也调用该方法。现在,异常不会被静默记录,您可以检查它是否被抛出。

You could do several things:

  1. Stub the logger and check whether the error was logged or not as an indicator whether or not the bug occurred. You can use TypeMock or Moles if the logger can't be easily replaced.
  2. Refactor the part inside the try block into its own method and call only that method inside the try block and make your unit test also call that method. Now the exception will not be silently logged and you can check whether or not it was thrown.
不一样的天空 2024-10-29 04:18:25

我认为最好的做法是模拟记录器,并断言记录器没有被调用进行传递。如果这是一个很大的变化,我假设记录器被用在很多地方,这将帮助您将来进行其他测试。为了快速修复,您可以在异常捕获器中引发一个事件,但我认为这不是一种非常“干净”的方法。

I think the best practice would be to mock out the logger, and assert that the logger has not been called for a pass. If it's a large change, I'm assuming the logger is used in a lot of places, which will help you with other tests in the future. For a quick-fix, you could raise an event in the exception catcher, but I don't think that's a very 'clean' way of doing it.

饭团 2024-10-29 04:18:25

恕我直言,任何包罗万象的都是错误的。您想要捕获您正在寻找的特定异常。单元测试也是为了让代码变得更好,所以你可以将其更改为

catch (ExpectedException ex)

IMHO any catch all is wrong. You want to catch specific Exceptions, that you are looking for. Unit-testing is also about making the code better, so you can change it to

catch (ExpectedException ex)
羁拥 2024-10-29 04:18:25

log 可能是一个注入的服务,这意味着 SomeClass 看起来像这样:

public final class SomeClass 
{
    private final Logger log;

    public SomeClass(Logger log)
    {
        this.log = log;
    }
}

所以在你的单元测试中,你可以传递一个假的 Logger (可能构造使用模拟框架),它允许您检测测试期间是否记录了警告。

如果 log 是全局变量,您可以使该全局变量可写,以便您可以以类似的方式替换测试中的记录器。

另一种选择是在单元测试中将 Handler 添加到 log 中,以检测 warn 调用(假设您正在使用 java.util.logging.Logger,但是情况似乎并非如此,因为该方法是 警告,而不是警告)。

The log could be an injected service, meaning that SomeClass looks like this:

public final class SomeClass 
{
    private final Logger log;

    public SomeClass(Logger log)
    {
        this.log = log;
    }
}

So in your unit test you could pass a fake Logger (possibly constructed with a mocking framework) which allows you to detect whether or not a warning was logged during the test.

If log is a global variable, you could make that global variable writable so that you can replace the logger in your tests in a similar way.

Yet another option is to add a Handler to log in your unit test for the purpose of detecting warn calls (assuming that you are using java.util.logging.Logger, but that doesn't seem to be the case because the method is warning, not warn).

两仪 2024-10-29 04:18:25

好吧,你公司的方法确实意味着你有时必须编写大量额外的测试,你可能会争论这是否是解决这个“明显的简单错误”的方法。但你这样做是有原因的。

我会这样看:

  • 你的方法应该有一定的效果。
  • 目前还没有这个效果。
  • 如果你想修复它并测试该方法是否有效,你需要检查效果。

因此,您可能应该编写整个代码,甚至使用外部系统,以证明该功能有效。或者在这种情况下,没有。

Well, your companies method does mean you sometimes have to write a lot of extra tests, and you could argue if this is the way to go for this "obvious easy bug". But you're doing it for a reason.

I'd look at it like this:

  • Your method should have a certain effect.
  • It does not have this effect at the moment.
  • If you want to fix it and test if the method works, you need to check the effect.

Therefore, you probably should write the whole code, even with the external systems, to proof the function works. Or in this case, doesn't.

相思碎 2024-10-29 04:18:25

在测试开始时为此类/记录器添加一个警告级别的附加记录器附加程序(并在结束时将其删除)。

然后运行 ​​someMethod 并检查新的附加程序是否仍然为空(然后没有异常)或有内容(然后有异常)。

Add an additional logger appender at warn level for this class/logger at the beginning of the test (and remove it at the end).

Then run the someMethod and check if the new appender is still empty (then there was no exception) or has content (then there was a exception).

十级心震 2024-10-29 04:18:25

您可以考虑使用 Boolean.toBoolean(String) 或 "true".equalsIgnoreCase(text)

如果您不希望出现此行为,您可能需要添加一个测试来显示“True”和“TRUE”被视为 false。

You might consider using Boolean.toBoolean(String) or "true".equalsIgnoreCase(text)

If you don't want this behaviour you might want to add a test which shows that "True" and "TRUE" are treated as false.

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