首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Mockito无法验证来自org.slf4j.Logger的多个方法调用

Mockito无法验证来自org.slf4j.Logger的多个方法调用
EN

Stack Overflow用户
提问于 2013-11-25 14:56:06
回答 3查看 5.8K关注 0票数 4

I有一个包含两个条件的方法。在每个条件下,调用Logger.error方法。验证该方法调用的第一个测试成功,但任何其他测试都在中失败。

想要但不是invoked...Actually,与这个模拟的交互是零的。

,有人知道为什么会这样吗?

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

代码语言:javascript
运行
复制
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");
        }
    }
}

测试:

代码语言:javascript
运行
复制
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");
    }
}

结果:

通缉但未被调用: packageName.XTest.whenSecondCaseErrorLogged(XTest.java:51)的loggerMock.error("latida");-> 实际上,这个模拟没有任何交互作用。

编辑:

我给出了一个简短的答案,为什么除了第一次考试外,所有的考试都没有通过comment of this answer考试。

我的问题解决方案

在测试中提供:

代码语言:javascript
运行
复制
public static Logger loggerMockStatic;  

而不是为所有测试创建一个实例,并在静态变量中提供该实例,并从Than开始使用静态loggerMockStatic。所以你会:

代码语言:javascript
运行
复制
    ...  
    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行,允许您测试常量(在本例中是记录器)行为。没有太多的污染和测试用例仍然很清楚。

正如我在this answer中解释的那样,“”方法为系统打开了漏洞。不需要有人将记录器设置为类,我总是希望系统按需要打开。只为测试需要提供一个setter是不需要的。测试应该适用于实现,而不是相反。

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

我和一些同事讨论过,有一个单独的类来进行日志记录就可以做到这一点。这样,常量就被隔离在另一个类中,您将能够只使用Mockito来检查行为。他们进一步指出,这样如果你想把日志发送到电子邮件中,就更容易改变了。

首先,如果您不打算在不久的将来在日志记录方式之间切换,我认为这是一个过早的模块化。

其次,仅使用Mockito +有另一个类和+3行代码与我的一行代码(logger.error(.)+使用PowerMockito )+,我将再次使用后者。在测试期间添加额外的依赖项不会使您的生产代码变得更慢和更大。也许在考虑继续集成和测试与其他阶段一样重要时,您可能会说这会使测试过程变得更慢和更笨重,但我会牺牲这一点--对我来说,这似乎不是什么大问题。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2013-11-27 21:55:45

这就是为什么这样做行不通的原因:

类X中的字段是静态的和最终的,允许只在第一次加载类时设置这个字段。这是危险的,因为我在我的第一个答复中所写的。在你的情况下,你很幸运,但这不是真的,但是.

Junit按照以下顺序执行测试用例: construct() whenFirstCaseErrorLogged() construct() whenSecondCaseErrorLogged()

现在让我们说,在第一次调用construct() XTest的字段之后,loggerMock指向位于地址0001的对象。然后,LoggerFactory使用该对象初始化x对象的记录器字段。然后从whenFirstCaseErrorLogged()调用whenFirstCaseErrorLogged,因为loggerMock和X::Logger都指向同一个对象。

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

下面是我的一些想法。也许它们会有帮助。我认为今后你应该重新考虑使用静态两次。当某件事不是常数的时候,你为什么要使它不变呢?在第二次运行之后,引用变量是否具有相同的值?当然,这是可能的,但这是非常不可能的。静态最终能否阻止您更改对象的状态?当然,它们只会阻止您将记录器重新分配到另一个实例。您在前面的注释中提到,您不希望代码的用户为您的记录器提供空引用。这是可以的,但您可以通过在提供异常时抛出异常或使用不同的空处理机制来防止这一点。

关于使用静态关键字,人们已经说了很多。有些人认为这是纯粹的邪恶,有些人不认为,有些人仍然爱单身:)

不管你想什么,你都要知道静态对测试和线程没有好处。当某些东西是静态的,如PI或euler数时,我使用静态final,但是对于具有可变状态的对象,我不使用静态final。对于不存储状态但只进行一些处理(解析、计数等)的实用程序类,我使用静态方法。并及时返回结果。一个很好的例子是像幂这样的数学函数。

(我认为这将是有用的;)

票数 1
EN

Stack Overflow用户

发布于 2013-11-25 15:16:57

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

票数 4
EN

Stack Overflow用户

发布于 2013-11-25 17:02:10

向类X添加一个方法,以允许设置记录器,并从其中删除final。那就在考试中做这样的事。

代码语言:javascript
运行
复制
@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");
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20196138

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档