Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/unit-testing/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java Mockito无法验证org.slf4j.Logger中方法的多次调用_Java_Unit Testing_Logging_Mockito_Powermock - Fatal编程技术网

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

Java Mockito无法验证org.slf4j.Logger中方法的多次调用,java,unit-testing,logging,mockito,powermock,Java,Unit Testing,Logging,Mockito,Powermock,我有一个包含2个条件的方法。在每种情况下,都会调用Logger.error方法。验证该方法调用的第一个测试成功,但任何其他测试都失败, 需要但未调用…实际上,与 这是个笑话 有人知道为什么会发生这种情况吗? 下面,我提供了一个示例类和一个单元测试来生成问题: package packageName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class X { private static fin

我有一个包含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");
        }
    }
}
测试:

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");
    }
}
结果:

需要但未调用:loggerMock.error(“latida”); ->在packageName.XTest.whenSecondCaseErrorLogged(XTest.java:51)
实际上,与这个模拟没有任何交互

编辑:
我简短地回答了为什么除了第一次考试以外的每一次考试都不及格

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

public static Logger loggerMockStatic;  
然后,只为所有测试创建一个实例,并在静态变量中提供它,然后从Than开始使用静态LoggerLockStatic。所以你会:

    ...  
    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);
    ...
并在验证方法中使用loggerMockStatic,而不是loggerMock

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

如我在中所述的“删除最终设置并提供设置器”方法会使系统面临漏洞。不需要有人将记录器设置为类,我总是希望系统能够根据需要打开。不希望仅为测试需要提供设置器。测试应该适用于实现,而不是相反

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

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

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

在class
X
中添加一个方法以允许设置记录器,并从中删除
final
。然后在测试中这样做

@Mock private Logger mockLogger;
private X toTest = new X();

...
@Before
public void setUp() throws Exception {
    toTest.setLogger(mockLogger);
}

@Test
public void logsRumpampamForFirstCall() throws Exception {
    toTest.execute(true);
    verify(mockLogger).error("rumpampam");
}

@Test
public void logsLatidaForOtherCalls() throws Exception {
    toTest.execute(false);
    verify(mockLogger).error("latida");
}

以下是这不起作用的原因:


类X中的字段是静态的和最终的,只允许在第一次加载类时设置此字段。这是危险的,因为我在第一个答案中写了什么。在你的情况下,你是幸运的,这不会发生,但

Junit按以下顺序执行测试用例: 构造() 当出现错误时() 构造() whenSecondCaseErrorLogged()时

现在让我们假设在第一次调用construct()XTest后,loggerLock字段指向位于地址0001处的对象。然后,LoggerFactory使用该对象初始化x对象的记录器字段。x、 然后从whenFirstCaseErrorLogged()调用error,这在finde中有效,因为LoggerLock和x::Logger都指向同一个对象

现在我们来看第二个构造()。LoggerLock已重新初始化,现在它指向另一个对象,假设该对象存储在内存中的地址0002处。这是一个不同于先前创建的新对象。现在,因为您的X::LOGGER是静态最终的,所以它不会被重新初始化,因此它仍然指向存储在地址0001处的对象。当您尝试验证在LoggerLock上调用的方法时,您将得到错误,因为在该对象上没有执行任何操作,而是调用了前一个对象的错误方法

以下是我的一些想法。也许他们会显得很有帮助。
我认为在将来你应该重新考虑使用静态两次。为什么你想让一个不恒定的东西保持恒定?第二次运行后,引用变量是否具有相同的值?这当然可能发生,但可能性很小。static final能否阻止您更改对象的状态?当然不是,它们只会阻止您将记录器重新分配到不同的实例。您在前面的评论中提到,您不希望代码的用户为您的记录器提供空引用。Tha