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

时间:2011-03-15 10:49:20

标签: java unit-testing legacy

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

  1. 编写单元测试,但未能清楚地显示存在错误
  2. 修复错误
  3. 重新运行测试以证明错误已得到修复
  4. 提交修复和测试以避免将来出现回归
  5. 现在我遇到了一段非常简单的遗留代码。情况如下:

    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,但不会失败......

    您可能会想到该方法的作用是什么?我可以测试该方法的效果。不幸的是,它与一些远程系统进行通信,因此这将需要一个巨大的集成测试,这对于这样一个小而且前瞻性的bug来说似乎有些过分。

    请注意,如果不是不可能在代码中进行任何不直接导致错误的更改,那将非常困难。因此,更改代码以使其更容易测试可能不是一种选择。这是非常可怕的遗留代码,每个人都非常害怕触摸它。

7 个答案:

答案 0 :(得分:2)

你可以做几件事:

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

答案 1 :(得分:2)

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

答案 2 :(得分:0)

恕我直言,任何一切都是错的。您想要捕获您正在寻找的特定异常。单元测试也是为了使代码更好,所以你可以将它改为

catch (ExpectedException ex)

答案 3 :(得分:0)

log可以是注入服务,这意味着SomeClass看起来像这样:

public final class SomeClass 
{
    private final Logger log;

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

因此,在您的单元测试中,您可以传递假Logger(可能使用mocking framework构建),以便您可以检测测试期间是否记录了警告。

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

另一种选择是在单元测试中添加Handlerlog,以便检测warn次来电(假设您正在使用java.util.logging.Logger,但是似乎并非如此,因为该方法是warning,而不是warn)。

答案 4 :(得分:0)

嗯,你的公司方法确实意味着你有时候必须编写很多额外的测试,你可以争论这是否是这种“明显容易出错”的方法。但是你这样做是有原因的。

我会这样看:

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

因此,您可能应该编写整个代码,即使使用外部系统,也可以证明函数有效。或者在这种情况下,不会。

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

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

如果您不想要此行为,则可能需要添加一个测试,该测试显示“True”和“TRUE”被视为false。