如何模拟 Final 类 并获得代码覆盖率

How to mock Final classes and have code coverage

我找不到解决我的 JUnit 问题的方法,所以我尽量简化它,希望它很容易理解。

基本上,我正在尝试测试这个 class :

public class PB {
    public int startProcessBuilder() {
        int status = 1;
        try {
            ProcessBuilder pb = new ProcessBuilder("java", "-jar", ".....");
            pb.directory(new File("/directory"));
            Process process = pb.start();
            status = process.waitFor();
        } catch (IOException | InterruptedException e) {
            System.out.println(e.getMessage());
        }
        return status;
    }
}

所以我想出了这个测试:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ ProcessBuilder.class, PB.class })
public class PBTest {

    private PB spyInstance = Mockito.spy(PB.class);
    private ProcessBuilder processBuilderMock = PowerMockito.mock(ProcessBuilder.class);
    private Process processMock = Mockito.mock(Process.class);

    @Before
    public void initialize() throws Exception {
        PowerMockito.whenNew(ProcessBuilder.class).withParameterTypes(String[].class).withArguments(anyVararg())
                .thenReturn(processBuilderMock);
        PowerMockito.doReturn(processMock).when(processBuilderMock).start();
    }

    @Test
    public void testStartProcessBuilder() throws Exception {
        assertThat(spyInstance.startProcessBuilder(), is(0));
    }
}

我知道我的测试 运行ning 是成功的,但在我工作的公司中,我们使用 jacoco 和 eclemma 来显示代码覆盖率并且显示所有代码是一个已知问题如果我们正在测试的 class 在 @PrepareForTest 注释中,则覆盖率为 0%。

所以我们现在使用了一个已知的解决方案,使用 MockitoJUnitRunner (http://www.notonlyanecmplace.com/make-eclemma-test-coverage-work-with-powermock/)

@RunWith(MockitoJUnitRunner.class)
@PrepareForTest({ ProcessBuilder.class, PB.class })
public class PBTest {

    private PB spyInstance = Mockito.spy(PB.class);
    private ProcessBuilder processBuilderMock = PowerMockito.mock(ProcessBuilder.class);
    private Process processMock = Mockito.mock(Process.class);

    @Rule
    public PowerMockRule rule = new PowerMockRule();

    static {
        PowerMockAgent.initializeIfNeeded();
    }

    @Before
    public void initialize() throws Exception {
        PowerMockito.whenNew(ProcessBuilder.class).withParameterTypes(String[].class).withArguments(anyVararg())
                .thenReturn(processBuilderMock);
        PowerMockito.doReturn(processMock).when(processBuilderMock).start();
    }

    @Test
    public void testStartProcessBuilder() throws Exception {
        assertThat(spyInstance.startProcessBuilder(), is(0));
    }
}

真正的问题来了: 当我尝试 运行 我的测试时,出现此异常: org.mockito.exceptions.misusing.NotAMockException:传递给 when() 的参数不是模拟!并显示此行:

PowerMockito.doReturn(processMock).when(processBuilderMock).start();

是的,显然 processBuilderMock 不是模拟而是 powermock,所以我尝试替换这两行

private ProcessBuilder processBuilderMock = PowerMockito.mock(ProcessBuilder.class);

PowerMockito.doReturn(processMock).when(processBuilderMock).start();

通过这个 :

private ProcessBuilder processBuilderMock = Mockito.mock(ProcessBuilder.class);

PowerMockito.doReturn(processMock).when(processBuilderMock).start();

但是当然:Cannot mock/spy class java.lang.ProcessBuilder... because it is a final class(可能是我首先使用 PowerMock 的原因)

我有哪些选择?

你可以设计PB class 以便于测试。一种方法是提取 ProcessBuilder 参数:

public class PB {
  public int startProcessBuilder(String... args) {
    try {
      ProcessBuilder pb = new ProcessBuilder(args);

稍后在测试中您可以使用一个小的 "Hello World" 测试 JAR:

new PB().startProcessBuilder("java", "-jar", "path-to-test-jar");

或使用标准的 echo 命令,无论 OS:

都应该有相同的语法
new PB().startProcessBuilder("echo", "Hello", "World");

您不需要模拟任何东西,您实际上使用模拟 JAR 调用模拟 Java 进程。

您费了那么大劲来提高覆盖率这一事实凸显了您当前的开发过程是有问题的。覆盖率本身并不是目标,它是一个可以让您对代码充满信心的指标。如果你必须通过避免 @PrepareForTest 来提升它,这有什么意义?