导读:本专栏主要分享同学们在XIAOJUSURVEY&北大开源实践课程的学习成果。
专栏背景:【XIAOJUSURVEY&北大】2024滴滴开源XIAOJUSURVEY北大软微开源实践课
文内项目Github:XIAOJUSURVEY
作者:shiyiting763
一、单元测试
(一) 什么是单元测试
单元测试是软件开发中的一种测试方法,它对软件中最小可测试单元(方法/函数/模块)进行独立的测试。单元测试独立于用户界面,关注内部逻辑和功能。
(二) 单元测试的作用
单元测试的主要目的是发现程序中的错误,确保代码的正确性。通过编写和运行单元测试,开发人员可以及早发现并修复bug,从而提高代码质量和开发效率。
二、单元测试框架——Junit
(一) JUnit介绍
1. 单元测试框架
单元测试框架是一种用于编写和运行单元测试的软件工具。单元测试框架提供了一个标准化的环境,用于组织和执行单元测试。
它的主要功能包括:
提供用例组织与执行:大量的测试用例堆砌在一起,容易产生了扩展性与维护性等问题
提供丰富的断言方法:用例执行完之后都需要将实际结果与预期结果相比较(断言),从而断定用例是否执行通过。
提供丰富的日志: 当测试用例执行失败时能抛出清晰的失败原因,当所有用例执行完成后能提供丰富的执行结果。例如,总执行时间、失败用例数、成功用例数等。
从这些特性来看单元测试框架的作用是:帮助我们更自动化完成测试,所以,它是自动化测试的基础。
2. Junit
JUnit 是 Java 语言中最流行和最广泛使用的单元测试框架之一。它提供了断言、测试套件和测试报告等功能。
JUnit 官网:JUnit
(二) JUnit安装
JUnit目前分两个版本:JUnit4 和 JUnit5。JUnit5在JUnit4上新增了一些特性,这里主要介绍JUnit4
安装:打开Maven项目的 pom.xml 文件,添加依赖:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
(三) JUnit编写单元测试
1. 编写单元测试
假如我要测试的类名称是
HelloWorld.Java
,那么我创建的测试类通常称为
HelloWorldTest.Java
。
如果涉及到覆盖率统计的话,名称不对有可能会被忽略掉
一个简单的单元测试用例
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class HelloWorldTest {
@Test
public void helloTest() {
//逻辑代码
HelloWorld helloWorld = new HelloWorld();
String result = helloWorld.sayHello();
assertEquals("Hello, World!", result);
}
private class HelloWorld {
public String sayHello() {
return "Hello, World!";
}
}
}
@Test:用来注释一个普通的方法为一条测试用例
assertEquals() :方法用于断言两个值是否相等
2. 测试功能模块
被测试类
HelloWorld.Java
public class HelloWorld {
public String sayHello(){
return "Hello, World!";
}
}
测试类
HelloWorldTest.Java
public class HelloWorldtTest {
@Test
public void testHello() {
HelloWorld helloWorld = new HelloWorld();
String result = helloWorld.sayHello();
assertEquals("Hello, World!", result);
}
}
先 new 出 HelloWorld 类的实例,调用 hello 方法,通过 assertEquals() 断言返回结果。
(四) JUnit断言
JUnit提供的断言方法:
方法
说明
assertArrayEquals(expecteds, actuals)
查看两个数组是否相等。
assertEquals(expected, actual)
查看两个对象是否相等。类似于字符串比较使用的equals()方法。
assertNotEquals(first, second)
查看两个对象是否不相等。
assertNull(object)
查看对象是否为空。
assertNotNull(object)
查看对象是否不为空。
assertSame(expected, actual)
查看两个对象的引用是否相等。类似于使用“==”比较两个对象。
assertNotSame(unexpected, actual)
查看两个对象的引用是否不相等。类似于使用“!=”比较两个对象。
assertTrue(condition)
查看运行结果是否为true。
assertFalse(condition)
查看运行结果是否为false。
assertThat(actual, matcher)
查看实际值是否满足指定的条件。
fail()
让测试失败。
(五) JUnit注解
JUnit常用的注解如下:
注解
说明
@Test
标识一条测试用例。 1. (expected=XXEception.class): 表示期望输出的结果是标识的一个Exception 2. (timeout=xxx):验证超时
@Ignore
忽略被标识的测试用例。
@Before
每一个测试方法之前运行。
@After
每一个测试方法之后运行。
@BefreClass
所有测试开始之前运行。
@AfterClass
所有测试结果之后运行。
三、万能的Powermock
(一) PowerMock介绍
PowerMock 扩展了其他流行的测试框架如 JUnit 和 Mockito,提供了更强大的功能来测试各种复杂的场景。它主要用于解决以下问题:
- 静态方法测试:PowerMock 可以测试静态方法、构造函数和静态初始化块。
- 私有方法测试:PowerMock 可以测试私有方法。
- final 类和方法测试:PowerMock 可以测试 final 类和 final 方法,即使它们不可覆盖。
- 单例模式测试:PowerMock 可以模拟单例类的行为,使得单元测试更加容易编写。
- 异常测试:PowerMock 可以更好地测试方法抛出的异常。
PowerMock实现了"一切皆可Mock"
PowerMock官网:Home · powermock/powermock Wiki · GitHub
(二) PowerMock配置
注解和使用场景
在使用
PowerMock
时需要针对不同场景添加对应注解,主要是
@RunWith
和
@PrepareForTest
注解。
注解添加和场景对应如下所示。
场景
注解
模拟final方法
@PrepareForTest
,
@RunWith
模拟静态方法
@PrepareForTest
,
@RunWith
模拟私有方法
@PrepareForTest
使用whenNew
@PrepareForTest
,
@RunWith
添加依赖
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.23.0</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-module-junit4</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
在引入依赖时,需要注意核对
Mockito
和
PowerMock
的版本对应关系,否则会报错。版本对应关系可以去PowerMock官网进行查询。通常情况下,如果引入的
mockito-core
版本为2.x,则
PowerMock
的api需要使用
powermock-api-mockito2
(三) PowerMock使用
1. mock public方法
public class Mock {
public String sayHello() {
return "Hello, World!";
}
}
public class MockTest {
@Test
public void sayHello2() {
// 创建被测试对象的Mock实例
Mock mock = mock(Mock.class);
// 设置模拟方法的返回值
when(mock.sayHello()).thenReturn("Hello, World!");
// 调用被测试方法
String result = mock.sayHello();
// 验证结果
verify(mock, times(1)).sayHello(); // 验证sayHello方法被调用了一次
assertEquals("Hello, World!", result);
}
}
经典的三步走:
a. 使用
PowerMockito.mock(方法所在类.class)
获取mock出来的对象,这里称之为mock实例。
mock实例的方法均为假方法,不对
mock实例进行任何操作的情况下,调用
mock实例的方法会返回(如果有返回值的话)返回值类型的默认值(零值,比如String
返回
null,Integer
返回0)。
b. 使用
PowerMockito.when(mock实例.方法).thenReturn()
设置想要返回的期望值,称为打桩
c. 断言
可以使用
whenNew()
来实现在程序中new一个对象时得到一个mock实例
在使用whenNew()时,需要添加@PrepareForTest注解,注解的内容是被测试类的Class对象。 如下所示。
@RunWith(PowerMockRunner.class)
@PrepareForTest({HelloWorld.class}) // 需要准备进行测试的类
public class HelloWorldTest {
@Test
public void testSayHello() throws Exception {
// 准备需要进行模拟的类
PowerMockito.mockStatic(HelloWorld.class);
// 模拟构造函数的行为
HelloWorld mockedInstance = PowerMockito.mock(HelloWorld.class);
whenNew(HelloWorld.class).withNoArguments().thenReturn(mockedInstance);
// 设置模拟方法的返回值
PowerMockito.when(mockedInstance.sayHello()).thenReturn("Hello, World!");
// 调用被测试方法
HelloWorld helloWorld = new HelloWorld();
String result = helloWorld.sayHello();
// 验证结果
assertEquals("Hello, World!", result);
}
}
2. mock final public方法
和mock public方法操作一致,只是需要添加@PrepareForTest注解 。如下所示。
public class Mock {
public final boolean isTrue() {
return true;
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(Mock.class)
public class PowerMockTest {
@Test
public void mockFinalPublic() {
Mock mock = PowerMockito.mock(Mock.class);
PowerMockito.when(mock.isTrue()).thenReturn(false);
assertThat(mock.isTrue(), is(false));
}
}
3. mock private方法
mock private方法打桩时,需要使用
PowerMockito.when(mock实例,"私有方法名",参数).thenReturn(期望返回值)
的形式设置mock实例的私有方法的返回值。
如果私有方法有参数,还需要在私有方法名后面添加参数占位符,比如
PowerMockito.when(mock实例,"私有方法名",anyInt()).thenReturn(期望返回值)
。
public class Mock {
public boolean isTrue() {
return returnTrue();
}
private boolean returnTrue() {
return true;
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(Mock.class)
public class MockTest {
@Test
public void isTrueTest() throws Exception {
Mock mock = PowerMockito.spy(new Mock());
when(mock, "returnTrue").thenReturn(false);
boolean result = mock.isTrue();
assertEquals(false, result);
}
}
如果仅仅只是验证一个私有方法,可以使用
Whitebox
来方便的调用私有方法
public class Mock {
private boolean returnTrue() {
return true;
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(Mock.class)
public class MockTest {
@Test
public void returnTrueTest() throws Exception {
Mock mock = new Mock();
boolean result = Whitebox.invokeMethod(mock, "returnTrue");
assertTrue(result);
}
}
4. 属性注入field
现有一个待测试的类UserServiceImpl,该类中注入了一个UserMapper的类实例。
@Service
public class UserServiceImpl {
@Autowried
private UserMapper userMapper;
........省略部分方法
}
对这个类进行测试的时候,我们往往不仅需要Mock测试类,还要Mock测试类中的属性,并保证mock实例在执行方法时,如果用到某个属性,真正操作的是我们Mock后的属性。也就是需要mock属性,并注入到mock类 。
这个功能的实现往往依赖两个注解:
@Mock:注解修饰会mock出来一个对象,这里mock出来的是UserMapper类实例。 @InjectMocks :注解会主动将已存在的mock对象注入到bean中,按名称注入,这个注解修饰在我们需要测试的类上。必须要手动new一个实例,不然单元测试会有问题。
@RunWith(PowerMockRunner.class)
public class UserServiceImplTest {
@Mock
private UserMapper userMapper;
@InjectMocks
private UserServiceImpl userServiceImpl = new UserServiceImpl();
}
@InjectMocks和@Mock注解配合使用可以帮我们做自动注入,但是这样在单机环境下由于spring容器在启动的时候会自动完成很多初始化工作,一来比较耗时,二来会去连接一些其他中间件比方说配置中心等,单机下就会出现异常。
那么我们就需要PowerMock的field方法来帮助我们做一些装配的工作,通常这部分内容我们会放在@Before下面,因为每个测试用例都会使用到。
@Before
public void setup() throws Exception(){
PowerMockito.field(UserServiceImpl.class, "userMapper").set(userService, userMapper);
}
四、轻量Mock工具——TestableMock
(一) TestableMock介绍
TestableMock
,一款特立独行的轻量Mock工具。能够快速地对各种方法,包括私有方法、静态方法、final方法进行mock,简化了mock的过程,实现模块之间的解耦,使得测试人员能够更加关注代码的功能和实现逻辑。
TestableMock
现在已不仅是一款轻量易上手的单元测试Mock工具,更是以简化Java单元测试为目标的综合辅助工具集,包含以下功能:
快速Mock任意调用:使被测类的任意方法调用快速替换为Mock方法,实现"指哪换哪",解决传统Mock工具使用繁琐的问题
访问被测类私有成员:使单元测试能直接调用和访问被测类的私有成员,解决私有成员初始化和私有方法测试的问题
快速构造参数对象:生成任意复杂嵌套的对象实例,并简化其内部成员赋值方式,解决被测方法参数初始化代码冗长的问题
辅助测试void方法:利用Mock校验器对方法的内部逻辑进行检查,解决无返回值方法难以实施单元测试的问题
快速构造集合对象:提供简洁易用的集合构造和常用操作方法,解决准备测试数据时Java集合初始化代码冗长的问题
在测试时使用TestableMock的一个重要原因是,在测试覆盖率时,PowerMock的@PrepareForTest会导致一些测试类被忽略,降低覆盖率。采用TestableMock来代替可以很好地解决这个问题
TestableMock官网:TestableMock (alibaba.github.io)
(二) TestableMock配置
在项目
pom.xml
文件中,增加
testable-all
依赖和
maven-surefire-plugin
配置,具体方法如下。
建议先添加一个标识TestableMock版本的
property
,便于统一管理:
<properties>
<testable.version>0.7.9</testable.version>
</properties>
在
dependencies
列表添加TestableMock依赖:
ependencies>
<dependency>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-all</artifactId>
<version>${testable.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
最后在
build
区域的
plugins
列表里添加
maven-surefire-plugin
插件(如果已包含此插件则只需添加
<argLine>
部分配置):
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
</configuration>
</plugin>
</plugins>
</build>
若项目同时还使用了
Jacoco
的
on-the-fly
模式(默认模式)统计单元测试覆盖率,则需在
<argLine>
配置中添加一个
@{argLine}
参数,添加后的配置如下:
<argLine>@{argLine} -javaagent:${settings.localRepository}/com
(三) TestableMock使用
TestableMock
让每个业务类(被测类)关联一组可复用的Mock方法集合(使用Mock容器类承载),并遵循约定优于配置的原则,按照规则自动在测试运行时替换被测类中的指定方法调用。
实际规则约定归纳起来只有两条:
- Mock非构造方法,拷贝原方法定义到Mock容器类,加
@MockInvoke
注解 - Mock构造方法,拷贝原方法定义到Mock容器类,返回值换成构造的类型,方法名随意,加
@MockNew
注解
具体使用方法如下。
1. 前置步骤,准备Mock容器
首先为测试类添加一个关联的Mock类型,作为承载其Mock方法的容器,最简单的做法是在测试类里添加一个名称为
Mock
的静态内部类。例如:
public class DemoTest {
public static class Mock {
// 放置Mock方法的地方
}
}
2. 覆写任意类的方法调用
在Mock容器类中定义一个有
@MockInvoke
注解的普通方法,使它与需覆写的方法名称、参数、返回值类型完全一致,并在注解的
targetClass
参数指定该方法原本所属对象类型。
此时被测类中所有对该需覆写方法的调用,将在单元测试运行时,将自动被替换为对上述自定义Mock方法的调用。
例如,被测类中有一处
"something".substring(0, 4)
调用,我们希望在运行测试的时候将它换成一个固定字符串,则只需在Mock容器类定义如下方法:
// 原方法签名为`String substring(int, int)`
// 调用此方法的对象`"something"`类型为`String`
@MockInvoke(targetClass = String.class)
private String substring(int i, int j) {
return "sub_string";
}
当遇到待覆写方法有重名时,可以将需覆写的方法名写到
@MockInvoke
注解的
targetMethod
参数里,这样Mock方法自身就可以随意命名了。
下面这个例子展示了
targetMethod
参数的用法,其效果与上述示例相同:
// 使用`targetMethod`指定需Mock的方法名
// 此方法本身现在可以随意命名,但方法参数依然需要遵循相同的匹配规则
@MockInvoke(targetClass = String.class, targetMethod = "substring")
private String use_any_mock_method_name(int i, int j) {
return "sub_string";
}
@MockInvoke适用于任何方法,包括任意类的public方法,被测类自身的成员方法,任意类的静态方法,任意类的final方法
3. 覆写任意类的new操作
在Mock容器类里定义一个返回值类型为要被创建的对象类型,且方法参数与要Mock的构造函数参数完全一致的方法,名称随意,然后加上
@MockNew
注解。
此时被测类中所有用
new
创建指定类的操作(并使用了与Mock方法参数一致的构造函数)将被替换为对该自定义方法的调用。
例如,在被测类中有一处
new BlackBox("something")
调用,希望在测试时将它换掉(通常是换成Mock对象,或换成使用测试参数创建的临时对象),则只需定义如下Mock方法:
// 要覆写的构造函数签名为`BlackBox(String)`
// Mock方法返回`BlackBox`类型对象,方法的名称随意起
@MockNew
private BlackBox createBlackBox(String text) {
return new BlackBox("mock_" + text);
}
4. 在Mock方法中区分调用来源
通过
TestableTool.MOCK_CONTEXT
变量为Mock方法注入“额外的上下文参数”,从而区分处理不同的调用场景。
例如,在测试用例中验证当被Mock方法返回不同结果时,对被测目标方法的影响:
@Test
public void testDemo() {
MOCK_CONTEXT.put("case", "data-ready");
assertEquals(true, demo());
MOCK_CONTEXT.put("case", "has-error");
assertEquals(false, demo());
}
在Mock方法中取出注入的参数,根据情况返回不同结果:
@MockInvoke
private Data mockDemo() {
switch((String)MOCK_CONTEXT.get("case")) {
case "data-ready":
return new Data();
case "has-error":
throw new NetworkException();
default:
return null;
}
}
五、自动化生成测试——好用的插件:Testme
(一) Testme介绍
Testme是一款免费的能够自动生成单元测试的插件,同时它本身有支持Powermock单测的模板。
优点:Spring 的 Bean 生成单测代码时,即使 @Component 这类注解标注,属性通过 Setter 注解注入时,也会自动给添加 @Mock 和 @InjectMock 这类属性。 缺点:默认模板会在生成的方法上都加上 throws Exception
官网地址:TestMe Plugin for JetBrains IDEs | JetBrains Marketplace
(二) Testme插件安装
安装插件
在需要生成单元测试的类上点击右键,Generate->TestMe->JUnit4(选择提供的模板之一)
(三) Testme使用模板
在生成样例代码时,也可以创建自己的模板,点击上图的
Configure
,进入Testme的模板创建页,可以创建或者修改自己的模板。下面是我觉得比较好用的一个模板,是基于PowerMock进行操作的:
#parse("TestMe macros.java")
#set($hasMocks=$PowerMockBuilder.hasMocks($TESTED_CLASS))
#set($mockBuilder = $PowerMockBuilder)
#if($PACKAGE_NAME)
package ${PACKAGE_NAME};
#end
import org.junit.Assert;
import org.junit.Test;
#if($hasMocks)
import static org.powermock.api.mockito.PowerMockito.*;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.junit.Before;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.junit.runner.RunWith;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.verify;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.modules.junit4.PowerMockRunner;
#end
/**
* @author xxx
* @date ${DATE} ${TIME}
*/
#parse("File Header.java")
@RunWith(PowerMockRunner.class)
@PowerMockIgnore("javax.management.*")
public class ${CLASS_NAME} {
#renderMockedFields($hasMocks, $TESTED_CLASS)
#renderTestSubjectInit($TESTED_CLASS,$TestSubjectUtils.hasTestableInstanceMethod($TESTED_CLASS.methods),$hasMocks)
#if($hasMocks)
@Before
public void setUp() {
MockitoAnnotations.${PowerMockBuilder.initMocksMethod}(this);
}
private static class Mock{
}
#end
#foreach($method in $TESTED_CLASS.methods)
#if($TestSubjectUtils.shouldBeTested($method))
@Test
public void #renderTestMethodName($method.name)()#if($method.methodExceptionTypes) throws $method.methodExceptionTypes#end {
#if($hasMocks && $PowerMockBuilder.shouldStub($method, $TESTED_CLASS))
#renderMockStubs($method, $TESTED_CLASS)
#end
#if($PowerMockBuilder.hasInternalMethodCall($method, $TESTED_CLASS))
#renderInternalMethodCallsStubs($method, $TESTED_CLASS)
#renderMethodCallWithSpy($method,$TESTED_CLASS.name)
#else
#renderMethodCall($method,$TESTED_CLASS.name)
#end
#if($hasMocks && $PowerMockBuilder.shouldVerify($method,$TESTED_CLASS))
#renderMockVerifies($method,$TESTED_CLASS)
#end
}
#end
#end
}
六、jacoco覆盖率生成
(一) jacoco介绍
代码覆盖(Code coverage)是软件测试中的一种度量,描述程序中源代码被测试的比例和程度,所得比例称为代码覆盖率。简单来理解,就是单元测试中代码执行量与代码总量之间的比率。
Java常用的单元测试覆盖率框架有:JaCoCo、EMMA和Cobertura
JaCoCo为基于Java VM的环境中的代码覆盖率分析提供标准技术。重点是提供一个轻量级,灵活且文档齐全的库,以与各种构建和开发工具集成。
JaCoCo官方文档:https://www.eclemma.org/jacoco/trunk/doc/index.html
(二) jacoco配置
引入Maven插件
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
</dependency>
配置该插件的执行标签<executions>
对于运行简单的单元测试,在执行标签中设置的两个目标可以正常工作。最低限度是设置准备代理(prepare-agent)和报告目标(report),配置如下:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.6</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<!--定义输出的文件夹-->
<outputDirectory>target/jacoco-report</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
prepare-agent: prepare-agent 目标在 JaCoCo 运行时记录执行数据。它记录了执行的行数、回溯的行数等。默认情况下,将执行数据写入文件target/jacoco-ut.exec。
report: report目标根据 JaCoCo 运行时记录的执行数据创建代码覆盖率报告。由于我们已经指定了阶段属性,报告将在测试阶段编译后创建。默认从文件target/jacoco-ut.exec中读取执行数据,将代码覆盖率报告写入目录target/site/jacoco/index.html。
<configuration>中可以定义输出的文件夹
其他所有配置的Goals,可以详见官网https://www.eclemma.org/jacoco/trunk/doc/maven.html
(三) jacoco使用
1. 生成报告
执行jacoco,命令是
mvn package
/
mvn clean test
,这两个命令都可以
最后,打开文件target/site/jacoco/index.html,即可查看覆盖率报告
2. 多模块工程覆盖率统计
假如一个代码是多模块工程,如下面的结构:
├── module1 │ └── pom.xml ├── module2 │ └── pom.xml ├── module3 │ └── pom.xml ├── pom.xml
每个模块都这么配置的话,生成的报告是各自独立的,即会生成3个报告,那么怎么把各个模块的代码覆盖率统计在一起,生成一个聚合的报告呢?
简单来说,分为两步:
a. 新建一个模块配置jacoco的report-aggregate
b. 这个模块需要引用所有的其他模块
具体做法如下:
a. 新建一个子模块作为聚合模块在聚合模块中配置jacoco聚合报告:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.5</version>
<executions>
<execution>
<id>my-report</id>
<phase>test</phase>
<goals>
<goal>report-aggregate</goal>
</goals>
</execution>
</executions>
</plugin>
b. 在聚合模块中引用其他模块(只有引用的模块的覆盖率才会被聚合到报告中)
<dependency>
<groupId>@project.groupId@</groupId>
<artifactId>module1</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>@project.groupId@</groupId>
<artifactId>module2</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>@project.groupId@</groupId>
<artifactId>module3</artifactId>
<version>${project.version}</version>
</dependency>
3. 消除lombok对覆盖率测试的影响
Lombok是一个Java库,它可以通过注解来简化Java代码的编写。其中,@Data注解可以自动生成JavaBean的getter、setter、equals、hashCode和toString方法。但是,使用@Data注解会影响代码覆盖率,因为自动生成的方法没有被测试覆盖到。
为了处理这个问题,最有效的方法是:
在项目的根目录下新建一个名字为lombok.config的文件,里面有如下的内容,
config.stopBubbling = true
lombok.addLombokGeneratedAnnotation = true
这个方法要求Lombok >= 1.16.14, jacoco>0.8.0
七、总结
本篇文章主要介绍了一个maven项目如何进行单元测试并统计覆盖率报告。总的说来,使用JUnit框架,采用Powermock + Testablemock的方式,利用Testme插件辅助生成单元测试,最后采用Jacoco生成覆盖率报告。
关于其中的每一个工具的使用,更加详细的可以参照官方文档,这里列出的是我认为在写一个项目时必要的使用方法,基本能够让项目的覆盖率达到70%以上。
关于我们
感谢看到最后,我们是一个多元、包容的社区,我们已有非常多的小伙伴在共建,欢迎你的加入。
Github:XIAOJUSURVEY
社区交流群
微信:
Star
开源不易,请star 一下 ❤️❤️❤️,你的支持是我们最大的动力。
版权归原作者 XIAOJUSURVEY 所有, 如有侵权,请联系我们删除。