如何通过单元测试证明这段代码中的错误
我们公司有一个习惯,当报告错误时,我们会执行以下步骤:
- 编写一个失败的单元测试,清楚地显示错误的存在
- 修复错误
- 重新运行测试以证明错误已修复
- 提交修复和测试避免将来出现回归
现在我遇到了一段带有非常简单错误的遗留代码。情况如下所示:
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:
- Write a unit test that fails clearly showing the bug exists
- Fix the bug
- Re-run the test to prove the bug has been fixed
- 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
您可以做几件事:
You could do several things:
我认为最好的做法是模拟记录器,并断言记录器没有被调用进行传递。如果这是一个很大的变化,我假设记录器被用在很多地方,这将帮助您将来进行其他测试。为了快速修复,您可以在异常捕获器中引发一个事件,但我认为这不是一种非常“干净”的方法。
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.
恕我直言,任何包罗万象的都是错误的。您想要捕获您正在寻找的特定异常。单元测试也是为了让代码变得更好,所以你可以将其更改为
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
log
可能是一个注入的服务,这意味着SomeClass
看起来像这样:所以在你的单元测试中,你可以传递一个假的
Logger
(可能构造使用模拟框架),它允许您检测测试期间是否记录了警告。如果
log
是全局变量,您可以使该全局变量可写,以便您可以以类似的方式替换测试中的记录器。另一种选择是在单元测试中将
Handler
添加到log
中,以检测warn
调用(假设您正在使用 java.util.logging.Logger,但是情况似乎并非如此,因为该方法是 警告,而不是警告
)。The
log
could be an injected service, meaning thatSomeClass
looks like this: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
tolog
in your unit test for the purpose of detectingwarn
calls (assuming that you are using java.util.logging.Logger, but that doesn't seem to be the case because the method is warning, notwarn
).好吧,你公司的方法确实意味着你有时必须编写大量额外的测试,你可能会争论这是否是解决这个“明显的简单错误”的方法。但你这样做是有原因的。
我会这样看:
因此,您可能应该编写整个代码,甚至使用外部系统,以证明该功能有效。或者在这种情况下,没有。
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:
Therefore, you probably should write the whole code, even with the external systems, to proof the function works. Or in this case, doesn't.
在测试开始时为此类/记录器添加一个警告级别的附加记录器附加程序(并在结束时将其删除)。
然后运行
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).您可以考虑使用 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.