解决Jacoco和PowerMock不兼容的问题

Posted 朱清云的技术博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了解决Jacoco和PowerMock不兼容的问题相关的知识,希望对你有一定的参考价值。

在使用PowerMock来写单元测试的时候,且单元测试里面下面的@PrepareForTest和@RunWith(PowerMockRunner.class)的时候,单元测试能成功跑出来,但是其生成的单元测试覆盖率为零。比如下面的代码:

package org.powermock.example;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;

import java.util.ArrayList;
import java.util.Properties;

import static org.assertj.core.api.Assertions.assertThat;
import static org.powermock.api.mockito.PowerMockito.doCallRealMethod;
import static org.powermock.api.mockito.PowerMockito.doReturn;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest(Configuration.class, Lamda.class)
public class SomeClassTest 

    @Before
    public void setUp() throws Exception 
        Whitebox.setInternalState(Configuration.class, "enabled", (Object) null);
    

    @Test
    public void shouldReturnSumIfEnabled() throws Exception 
        mockStatic(Configuration.class);

        Properties properties = new Properties();
        properties.put("enabled", "true");

        doReturn(properties).when(Configuration.class, "readProperties");
        doCallRealMethod().when(Configuration.class, "isEnabled");
        doCallRealMethod().when(Configuration.class, "loadFromProperties");

        assertThat(new SomeClass().add(1, 5)).isEqualTo(6);
    

    @Test
    public void shouldReturnZeroIfDisabled() throws Exception 
        mockStatic(Configuration.class);

        Properties properties = new Properties();
        properties.put("enabled", "false");

        doReturn(properties).when(Configuration.class, "readProperties");
        doCallRealMethod().when(Configuration.class, "isEnabled");
        doCallRealMethod().when(Configuration.class, "loadFromProperties");

        assertThat(new SomeClass().add(1, 5)).isEqualTo(0);
    

    @Test(expected = RuntimeException.class)
    public void shouldC() 
        mockStatic(Lamda.class);
        when(Lamda.capitalize(Mockito.anyString())).thenReturn("01234567890");

        ArrayList<String> in = new ArrayList<>();

        in.add("----");
        in.add(null);

        new Lamda().validate(in);
    

package org.powermock.example;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

import lombok.Data;



@Data
public class Configuration 

	private String name;
    private static Boolean enabled;

    public static boolean isEnabled() 
        if (enabled == null)
            loadFromProperties();
        
        return enabled;
    

    private static void loadFromProperties() 
        Properties properties = readProperties();
        enabled = "true".equals(properties.getProperty("enabled"));
    

    private static Properties readProperties() 
        Properties properties = new Properties();
        try 
            properties.load(new FileInputStream("some.properties"));
         catch (IOException e) 
            e.printStackTrace();
        
        return properties;
    


package org.powermock.example;

import java.util.List;
import java.util.function.Consumer;

public class Lamda 

    public static String capitalize(String in) 
        String result = "";
        String[] a = in.split(",");
        for (String s : a) 
            result += s.substring(0, 1).toUpperCase() + s.substring(1, s.length());
        
        return result;
    

    public void validate(List<String> in) 
        doWithList(in, s -> 
            if (s != null && s.length() > 10) 
                throw new RuntimeException("");
            
        );
    

    private void doWithList(List<String> in, Consumer<String> consumer) 
        in.stream().map(Lamda::capitalize).forEach(consumer);
    



package org.powermock.example;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

/**
 *
 */
@Data

public class SomeClass 
    public int add(int x, int y) 
        if (Configuration.isEnabled()) 
            return x + y;
        
        return 0;
    


那么应该如何做呢?Pom.xml的配置应该注意哪些点呢?

#1. 如果pom.xml还继承了parent pom,确保的你的parent pom里面没有plugin的配置
否则会报下面的类似的错误。

aused by: java.lang.IllegalStateException: Class xxxx.class is already instrumented.
        at org.jacoco.agent.rt.internal_290345e.core.internal.instr.InstrSupport.assertNotInstrumented(InstrSupport.java:176)
        at org.jacoco.agent.rt.internal_290345e.core.internal.instr.ClassInstrumenter.visitField(ClassInstrumenter.java:55)
        at org.jacoco.agent.rt.internal_290345e.asm.ClassVisitor.visitField(ClassVisitor.java:294)
        at org.jacoco.agent.rt.internal_290345e.asm.ClassReader.readField(ClassReader.java:883)
        at org.jacoco.agent.rt.internal_290345e.asm.ClassReader.accept(ClassReader.java:694)
        at org.jacoco.agent.rt.internal_290345e.asm.ClassReader.accept(ClassReader.java:500)
        at org.jacoco.agent.rt.internal_290345e.core.instr.Instrumenter.instrument(Instrumenter.java:90)
        at org.jacoco.agent.rt.internal_290345e.core.instr.Instrumenter.instrument(Instrumenter.java:108)

#2 确保你的dependency中有下面的依赖

 <dependency>
     <groupId>org.jacoco</groupId>
     <artifactId>org.jacoco.agent</artifactId>
     <version>$jacoco.version</version>
     <classifier>runtime</classifier>
</dependency>

否则会报下面的错误:

com.siact.product.jwp.module.user.service.UserTest  Time elapsed: 0.073 sec  <<< ERROR!
java.lang.NoClassDefFoundError: org/jacoco/agent/rt/internal_290345e/Offline
Caused by: java.lang.ClassNotFoundException: org.jacoco.agent.rt.internal_290345e.Offline

#3 确保你的maven-surefire-plugin的生成的acoco-agent.destfile值和jacoco-maven-plugin中的dataFile一致

#4 确保你的PowerMock的版本和Mockito的版本能匹配上,符合下面的列表

#5 确保你的是用了org.jacoco offline的模式。

   <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>$jacoco.version</version>
                <executions>
                    <execution>
                        <id>default-instrument</id>
                        <goals>
                            <goal>instrument</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>default-restore-instrumented-classes</id>
                        <goals>
                            <goal>restore-instrumented-classes</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>report</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                        <configuration>
                            <dataFile>$project.build.directory/coverage.exec</dataFile>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

下面上整个pom.xml的文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-examples</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>jacoco-offline</artifactId>
    <name>JaCoCo Offline with PowerMock</name>
    <description>
        Example how to get code coverage with PowerMock
    </description>

    <properties>
        <jacoco.version>0.7.7.201606060606</jacoco.version>

        <!-- Used to locate the profile specific configuration file. -->
        <build.profile.id>dev</build.profile.id>

        <jacoco.it.execution.data.file>$project.build.directory/coverage-reports/jacoco-it.exec</jacoco.it.execution.data.file>
        <jacoco.ut.execution.data.file>$project.build.directory/coverage-reports/jacoco-ut.exec</jacoco.ut.execution.data.file>

        <jdk.version>1.8</jdk.version>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <!-- Only unit tests are run by default. -->
        <skip.unit.tests>false</skip.unit.tests>

    </properties>

    <dependencies>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito</artifactId>
        </dependency>
        <dependency>
            <groupId>org.powermock<Android 单元测试实战—— 基于Cobertra&sonarqube的单元测试覆盖率统计

Android 单元测试实战—— 基于Cobertra&sonarqube的单元测试覆盖率统计

gradle、sonarqube 和 jacoco 插件的哪些版本兼容

使用 JaCoCo 和 Gradle 进行离线检测

JaCoCo SonarQube 不兼容版本 1007

PowerMock 有啥好的替代品吗?