Mockito 无法验证 org.slf4j.Logger 中方法的多次调用

des*_*pot 4 java logging unit-testing mockito powermock

我有一个包含 2 个条件的方法。在每种情况下都会调用 Logger.error 方法。第一个测试(验证该方法的调用)成功,但任何其他测试都会失败

想要但没有调用...实际上,与此模拟的交互为零。

有谁知道为什么会发生这种情况?

下面,我提供了一个示例类和一个将生成问题的单元测试:

package packageName;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class X {

    private static final Logger LOGGER = LoggerFactory.getLogger(X.class);

    public void execute(boolean handle1stCase) {
        if (handle1stCase) {
            LOGGER.error("rumpampam");
        } else {
            LOGGER.error("latida");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

考试:

package packageName;

import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
import static org.powermock.api.mockito.PowerMockito.mockStatic;

@RunWith(PowerMockRunner.class)
@PrepareForTest({LoggerFactory.class})
public class XTest {

    @Mock
    private Logger loggerMock;

    private X x;

    @Before
    public void construct() {
        MockitoAnnotations.initMocks(this);

        mockStatic(LoggerFactory.class);
        when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock);

        x = new X();
    }

    @Test
    public void whenFirstCaseErrorLogged() throws Exception {
        x.execute(true);
        verify(loggerMock, times(1)).error("rumpampam");
    }

    @Test
    public void whenSecondCaseErrorLogged() throws Exception {
        x.execute(false);
        verify(loggerMock, times(1)).error("latida");
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

想要但没有调用:loggerMock.error("latida"); -> at packageName.XTest.whenSecondCaseErrorLogged(XTest.java:51)
实际上,与此模拟的交互为零。

编辑:我在这个答案的评论
中给出了简短的答案,说明为什么除了第一个测试之外的每个测试都失败了。

我对问题的解决方案
在测试中提供:

public static Logger loggerMockStatic;  
Run Code Online (Sandbox Code Playgroud)

然后为所有测试仅创建一个实例并将其提供在静态变量中,然后使用静态 loggerMockStatic。所以你会:

    ...  
    MockitoAnnotations.initMocks(this);

    if (loggerMockStatic == null) {
        loggerMockStatic = loggerMock;
    }

    mockStatic(LoggerFactory.class);
    //when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock);
    when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMockStatic);
    ...
Run Code Online (Sandbox Code Playgroud)

并在验证方法中使用 loggerMockStatic 而不是 loggerMock。

关于方法的一些想法
对我来说这很好,因为
1.它不会破坏设计(如果您认为所需的变量应该是一个常量,那么它会保持这种状态)。
2. 测试中仅添加了 4 行,可让您测试常量(在本例中为记录器)行为。没有太多污染,测试用例仍然清晰。

正如我在这个答案中解释的那样,“删除最终并提供设置器”方法会导致系统存在漏洞。不需要有人将记录器设置为类,我总是希望系统根据需要打开。仅仅为了测试的需要而提供设置器是不期望的。测试应该为实现工作,而不是相反。

特别是在测试日志记录时,我认为在一般(大多数)情况下不应测试日志记录。日志记录应该是应用程序的一个方面。当您有其他输出要测试某个路径时,应该测试这些输出。但在这种情况下(也许还有其他情况),某个路径没有其他输出,例如在特定条件下记录和返回,则需要测试日志(根据我的说法)。我想始终知道即使有人更改条件,日志消息仍将被记录。如果没有日志,并且如果有人以错误的方式更改条件,则将无法知道错误是否存在于这段代码中(除非进行调试)。

我正在与一些同事讨论,有一个单独的类来进行日志记录可以解决问题。这样,常量就被隔离在另一个类中,并且您将能够仅使用 Mockito 检查行为。他们进一步指出,这样如果您想将日志发送到电子邮件,更改会更容易。
首先,如果您不打算在不久的将来在日志记录方式之间进行切换,我认为这是一个不成熟的模块化。
其次,仅使用 Mockito + 具有另一个类和 + 3 行代码 VS 我的一行代码 (logger.error(...)) + 使用 PowerMockito,我会再次使用后者。在测试期间添加额外的依赖项不会使您的生产代码变得更慢和更庞大。也许当考虑继续集成并且测试也与其他阶段一样重要时,您可能会说这会使测试时的过程变得更慢、更庞大,但我会牺牲这一点——这对我来说似乎没什么大不了的。

Luk*_*uke 5

您的记录器是静态的,因此它是在加载类时加载的,而不是在初始化对象时加载的。你无法保证你的模拟会按时准备好,有时它可能起作用,有时则不起作用。