解决Jacoco和PowerMock不兼容的问题
在使用PowerMock来写单元测试的时候,且单元测试里面下面的@PrepareForTest和@RunWith(PowerMockRunner.class)的时候,单元测试能成功跑出来,但是其生成的单元测试覆盖率为零。那如何解决呢?
在使用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</groupId>
<artifactId>powermock-core</artifactId>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
</dependency>
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>org.jacoco.agent</artifactId>
<version>${jacoco.version}</version>
<classifier>runtime</classifier>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<systemPropertyVariables>
<jacoco-agent.destfile>${project.build.directory}/coverage.exec</jacoco-agent.destfile>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</project>
上面是一种PowerMock和Mockito的搭配方式,除此之外还有下面的搭配方式。
<!-- JUnit5 and PowerMock Begin -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>1.7.0RC2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-easymock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>org.jacoco.agent</artifactId>
<classifier>runtime</classifier>
<scope>test</scope>
<version>${jacoco.version}</version>
</dependency>
<!-- JUnit5 and PowerMock End -->
其版本信息如下:
<jacoco.version>0.8.0</jacoco.version>
<powermock.version>1.6.3</powermock.version>
<mockito.version>3.9.0</mockito.version>
<powermock.api.mockito2.version>1.7.0RC2</powermock.api.mockito2.version>
还有其他的组合
<!-- tests -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.uuid</groupId>
<artifactId>java-uuid-generator</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.17</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.5.15</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.5.15</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4-rule</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-classloading-xstream</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${version.spring.framework}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${version.spring.framework}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.camunda.bpm</groupId>
<artifactId>camunda-engine-rest-core</artifactId>
<version>${project.version}</version>
<scope>test</scope>
<classifier>tests</classifier>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${version.jetty}</version>
<scope>test</scope>
</dependency>
最后扔一个彩蛋,如果在项目中使用了lombok,在DTO或者Bean的包下,添加一个名字为
lombok.config的文件,并在里面输入下面的配置:
config.stopBubbling = true
lombok.addLombokGeneratedAnnotation = true
这样,以lombok的@Data注解生成的类,其会被lombo的字节码编译环境自动嵌入@lombok.generated 注解,在0.8.0版本以上的Jacoco中,其响应的DTO和Entity就不会记录到Jacoco的覆盖率当中,从而大大提高了代码覆盖率!
其中config.stopBubbling = true的作用是这种自动化添加@lombok.generated 注解的功能不会蔓延和扩展到父包里面~
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)