如何模拟Final类并覆盖代码

时间:2018-11-26 16:18:28

标签: java junit code-coverage powermock processbuilder

我无法找到解决JUnit问题的解决方案,因此我已尝试将其简化到最大程度,以便希望它易于理解。

基本上,我正在尝试测试此类:

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));
    }
}

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

所以有一段时间,我们正在使用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的原因)

我有什么选择?

1 个答案:

答案 0 :(得分:2)

您可以将PB类设计为易于测试。一种方法是提取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命令,无论使用什么操作系统,该命令都应具有相同的语法:

new PB().startProcessBuilder("echo", "Hello", "World");

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

要增加覆盖面会遇到很多麻烦,这一事实表明您当前的开发过程值得商question。覆盖范围本身并不是目标,它是使您对代码充满信心的度量标准。如果您必须避免使用@PrepareForTest来提高效率,那有什么意义呢?