【问题标题】:PowerMockito final method invoke verification not workingPowerMockito 最终方法调用验证不起作用
【发布时间】:2016-03-11 00:25:10
【问题描述】:

我正在测试一个在 AtomicBoolean 值更改之前一直运行的 Task 类。我正在使用 PowerMockito,因为 AtomicBoolean 实例的 get() 方法是最终的。

MyTaskTest 看起来像这样:

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.util.concurrent.atomic.AtomicBoolean;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(AtomicBoolean.class)
public class MyTaskTest {

    private AtomicBoolean stopReceiving;
    private MyTask task;

    @Before
    public void setUp() {
        stopReceiving = PowerMockito.mock(AtomicBoolean.class);
        task = new MyTask(stopReceiving);
    }

    @Test
    public void test_run_when_StopReceivingIsFalseFromTheBeginning_should_invokeGetOnlyOnce() {
        // given
        PowerMockito.when(stopReceiving.get()).thenReturn(false);
        // when
        task.run();
        // then
        verify(stopReceiving, times(1)).get();
    }

}

MyTask 类看起来像这样:

import java.util.concurrent.atomic.AtomicBoolean;

public class MyTask implements Runnable {

    private final AtomicBoolean stopReceiving;

    MyTask(AtomicBoolean stopReceiving) {
        if (stopReceiving == null) {
            throw new NullPointerException("stopReceiving is null. This argument should not be null");
        }

        this.stopReceiving = stopReceiving;
    }

    @Override
    public void run() {
        while (stopReceiving.get() == true) {
            System.out.println("I should not get to here!");
        }
    }
}

这里的问题是检查 verify(stopReceiving, times(1)).get(); 失败:

需要但未调用 java.util.concurrent.atomic.AtomicBoolean.get();其实有 与此模拟的零交互。

这对我来说没有意义,因为它显然是在 run() 方法中调用的。我什至调试了它,它通过了run 方法,但不知何故并没有增加它的调用计数器。我在这里做错了什么?

附:我正在使用 PowerMockito 1.6.2 版

我尝试了什么

  • 将验证调用更改为在没有times(1) 的情况下调用。没有帮助
  • 将 powermockito 版本升级到 1.6.3。没有帮助
  • MyTask 作为内部类添加到 MyTaskTest 中,这以某种方式起作用,这意味着验证调用已通过,但我希望将 MyTask 包含在其他文件作为单独的类,这仍然不起作用。

【问题讨论】:

    标签: java junit mockito powermock


    【解决方案1】:

    你遇到了这个麻烦,因为你试图模拟一个系统类。如specified in the PowerMock documentation,PowerMock 无法准备系统类进行测试,因此您需要准备调用系统类的类,而不是像您在答案中所做的那样。

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(MyTask.class)
    public class MyTaskTest {
    

    幕后: Normal Mockito 通过动态创建您正在模拟的类的子类并返回它来工作; Mockito 的委托是通过正常的多态性发生的。使用final 方法,Java 不需要为动态调度执行虚拟方法查找; Java 知道在编译时要调用哪个实现,并在 Mockito 介入之前通过静态调度调用实现。

    PowerMock 通过重写(字节码操作)包含最终方法的类来解决此问题,以便实现更改为委托给 EasyMock 或 Mockito 存根和模拟;但是,为了使其生效,PowerMock 必须尽早在类路径中安装修改后的类(此处为AtomicBoolean),以覆盖它可以找到的任何其他实现。

    但是,您永远不能安装优先级高于系统类的替换,因为它们已经加载到根类加载器中;没有什么会先发制人。 PowerMock 通过重写您的被测系统来解决问题,将对 AtomicBoolean.get() 的调用替换为对 PowerMock 可以修改的替换方法的调用。因此,并非“PowerMockito 以某种方式将最终方法视为特殊方法”,而是javac 将它们视为特殊方法(使用静态调度调用)而 PowerMock 将系统类视为特殊方法(因为它可以'不要以通常的方式覆盖它们)。


    相关阅读: Lukáš Krejčí 的"The Dark Powers of PowerMock"

    旁注:

    • 特别是对于您的用例,您可能根本不需要模拟 AtomicBoolean;在不同的线程上使用真正的线程,测试线程是否存活,设置布尔值,并测试线程是否死亡。您可能需要 poll over time,就像 Mockito 的 timeout 验证模式一样。
    • 不要忘记stopReceiving.get() == true 始终可以替换为stopReceiving.get()

    【讨论】:

    • 感谢您的回答 :) 您给我的“模拟系统类”链接为我指明了正确的方向。我发现我使用的解决方案非常好。附注:我没有发现在单元测试中使用真实对象,尤其是在为类编写单元测试时超时。我知道stopReceiving.get() == truestopReceiving.get() 相同,这只是我的风格偏好,但感谢您指出;)
    • @Damir 我认为这种情况纯粹是对模拟的过度使用;尽管从您自己的系统中避免易碎的“真实对象”通常是一个好主意,但如果您不能信任 AtomicBoolean 的直接行为,那么您如何信任 ArrayList、Integer 或 ClassLoader?谁保证你的模拟行为真实?允许自己在单元测试中使用经过良好测试的库,尤其是系统库,可能会使您的测试更清晰、更易于维护。
    • 我理解你,但对于这种特殊情况,我必须创建新线程,就像你说的那样,我会在特定时间后更改原子布尔值。告诉我的原子布尔模拟首先返回“true”两次,然后在调用 get() 方法时返回“false”,然后超时执行此操作,对我来说更方便。我会在以后的单元测试中尝试使用您的输入:)
    【解决方案2】:

    MyTask.class 添加到@PrepareForTest 注释解决了这个问题。

    ...
    @RunWith(PowerMockRunner.class)
    @PrepareForTest({AtomicBoolean.class, MyTask.class})
    public class MyTaskTest {
    ...
    

    我找不到任何关于为什么需要这样做的信息。唯一的解释是 PowerMockito 以某种方式将 final 方法视为特殊方法,因此为了验证工作,您需要添加一个使用带有 final 方法调用的模拟的类嘲笑。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-18
      • 2019-07-22
      • 2014-06-29
      • 1970-01-01
      • 2012-01-29
      相关资源
      最近更新 更多