【问题标题】:How to avoid mock object's method calls in strings used for logging?如何避免在用于记录的字符串中调用模拟对象的方法?
【发布时间】:2013-10-25 17:37:01
【问题描述】:

我编写了一个测试方法,其中有一个模拟对象(比如 mockA)。我可以期待 mockA 对实际程序逻辑的方法调用。但是,我的程序的一部分也有日志记录,需要字符串形式的对象信息。在创建字符串消息时,会在对象上调用一些不必要的方法。因此,在运行测试时,这些方法调用会导致测试失败。 这是一个例子。

public class Example {
    public int method(Foo foo) {
       int a = foo.doSomething(); //required for program.
       String logMessage = "foo did something." + foo.getA() + foo.getB().getC();
       logger.log(logFile, logMessage);
       return a;
    }
}

这是示例测试方法。

@Test
public void testMethod() {
   int something = 0;
   Foo mockFoo = EasyMock.createMock(Foo.class);
   expect(mockFoo.doSomething()).andReturn(something);
   EasyMock.replay(mockFoo);
   assertEquals(new Example().method(mockFoo), something);
   EasyMock.verify(mockFoo);
}

这是对 foo.getA() 的意外方法调用。 如果我为 Foo.class 创建一个不错的模拟,它会给我 foo.getB().getC() 的空指针异常,因为 B 是 foo 中的一个对象。我不可能为 foo 中的所有对象创建漂亮的模拟。

有没有办法防止这种用于记录的字符串操作?或者,可以做什么?

谢谢

【问题讨论】:

    标签: java unit-testing junit easymock powermock


    【解决方案1】:

    有两种方法。首先,您模拟与Foo 正确交互所需的所有内容(如其他答案中所述),或者其次,您提取分离依赖项的复杂性。

    在您的示例中,复杂性在于消息创建。您可以:

    class FooLogMessageFactory {
        public string createLogSomethingMessage(Foo foo) {
            return "foo did something." + foo.getA() + foo.getB().getC();
        }
    }
    

    或单独的类中的整个日志记录部分:

    class FooDedicatedLogger {
        public void logOnSomething(Foo foo) {
            String message = "foo did something." + foo.getA() + foo.getB().getC();
            logger.log(logFile, message);
        }
    }
    

    这当然作为构造函数依赖注入到您的Example 类中。然后在测试中,您可以轻松地模拟它并完全忘记日志消息的创建(应该如此,因为它与实际测试方法的作用无关)。

    现在,选择哪种方法很大程度上取决于所涉及对象的复杂性(即实际构建日志消息有多困难?比设置几个额外的模拟调用更困难?)。 p>

    【讨论】:

    • 是的,这两种方法都很好。他们应该工作。谢谢你,吉米。
    【解决方案2】:

    Mockito 怎么样? 查看返回深度存根的可能性here

    (尤其是您不应该这样做的部分;)),那么您可以将测试编写为:

    @Test
    public void testMethod() {
       int something = 0;
       Foo mockFoo = mock(Foo.class, RETURNS_DEEP_STUBS);
       when(mockFoo.doSomething()).thenReturn(something);
       assertEquals(new Example().method(mockFoo), something);
       verify(mockFoo);
    }
    

    我在 Easymock 中找不到任何类似的技术,答案here 表明没有任何东西。


    另一个想法:

    如果您可以重构代码并使用 SLF4J 作为日志框架,则可以在 logger 调用中编写 Logging 消息,并且仅根据 Loglevel 执行此操作。在测试中将您的日志级别提高到 NONE 并且 foo.getA() ... 将不会被执行。

    public int method(Foo foo) {
       int a = foo.doSomething(); //required for program.
       logger.error("foo did something {}" , foo.getA() + foo.getB().getC());
       return a;
    }
    

    【讨论】:

    • 感谢您的想法,呃。我一直在使用 EasyMock,所以继续使用 Mockito 会有点不舒服。第二个想法涉及在方法调用中创建日志消息,如果我想在其他地方使用这些字符串,这可能不起作用。 jimmy_keen 给出的答案有帮助。谢谢。
    【解决方案3】:

    你可以这样做

    B b = EasyMock.createMock(B.class);

    expect(mockFoo.getB()).andReturn(b);

    所以foo.getB() 会给你模拟对象。所以不会有 NPE。

    【讨论】:

    • 是的,我可以做到,但正如我在问题本身中提到的,该对象可能不仅包含一个,还包含许多其他对象。因此,如果字符串类似于 foo.getB().getC() + foo.getX().getY().getZ() 等等......这不是一个可行的解决方案。
    • 如果你使用 log4j 或类似的,你可以检查 isXXXXEnabled() 如果是,你可以做你正在做的字符串操作和实际的日志记录。所以在你的测试中有一个不同的日志级别,所以部分代码永远不会被执行。
    • 感谢您的贡献。 :-)
    猜你喜欢
    • 2021-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多