1. 概述
在这个教程中,我们将构建一个Maven多模块项目。在这个项目中,服务和控制器将分布在不同的模块中。然后,我们将编写一些测试,并使用Jacoco来计算代码覆盖率。
2. 服务层
2.1. 服务类
我们将创建服务并添加一些方法:
@Service
class MyService {
String unitTestedOnly() {
return "unit tested only";
}
String coveredByUnitAndIntegrationTests() {
return "covered by unit and integration tests";
}
String coveredByIntegrationTest() {
return "covered by integration test";
}
String notTested() {
return "not tested";
}
}
这些方法的命名表明:
- 在同一层的单元测试(/java-unit-testing-best-practices)将测试
unitTestedOnly()
方法。 - 单元测试将测试
coveredByUnitAndIntegrationTests()
。控制器模块中的一个集成测试也会覆盖这个方法的代码。 - 集成测试将覆盖
coveredByIntegrationTest()
。然而,没有单元测试会测试这个方法。 -
notTested()
方法没有任何测试覆盖。
2.2. 单元测试
现在让我们编写相应的单元测试:
class MyServiceUnitTest {
MyService myService = new MyService();
@Test
void whenUnitTestedOnly_thenCorrectText() {
assertEquals("unit tested only", myService.unitTestedOnly());
}
@Test
void whenTestedMethod_thenCorrectText() {
assertEquals("covered by unit and integration tests", myService.coveredByUnitAndIntegrationTests());
}
}
测试只是检查方法的输出是否符合预期。
2.3. Surefire插件配置
我们将使用Maven的Surefire插件来运行单元测试。我们将在服务模块的pom.xml
中配置它:
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
</configuration>
</plugin>
</plugins>
3. 控制器层
接下来,我们在多模块应用中添加一个控制器层。
3.1. 控制器类
让我们添加控制器类:
@RestController
class MyController {
private final MyService myService;
public MyController(MyService myService) {
this.myService = myService;
}
@GetMapping("/tested")
String fullyTested() {
return myService.coveredByUnitAndIntegrationTests();
}
@GetMapping("/indirecttest")
String indirectlyTestingServiceMethod() {
return myService.coveredByIntegrationTest();
}
@GetMapping("/nottested")
String notTested() {
return myService.notTested();
}
}
fullyTested()
和indirectlyTestingServiceMethod()
方法将由集成测试进行测试。因此,**这些测试将覆盖服务方法coveredByUnitAndIntegrationTests()
和coveredByIntegrationTest()
**。另一方面,我们将不为notTested()
编写任何测试。
3.2. 集成测试
现在我们可以测试我们的RestController:
@SpringBootTest(classes = MyApplication.class)
@AutoConfigureMockMvc
class MyControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
void whenFullyTested_ThenCorrectText() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/tested"))
.andExpect(MockMvcResultMatchers.status()
.isOk())
.andExpect(MockMvcResultMatchers.content()
.string("covered by unit and integration tests"));
}
@Test
void whenIndirectlyTestingServiceMethod_ThenCorrectText() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/indirecttest"))
.andExpect(MockMvcResultMatchers.status()
.isOk())
.andExpect(MockMvcResultMatchers.content()
.string("covered by integration test"));
}
}
在这些测试中,我们启动应用程序服务器并发送请求。然后,我们检查输出是否正确。
3.3. Failsafe插件配置
我们将使用Maven的Failsafe插件来运行集成测试。最后一步是在控制器模块的pom.xml
中配置它:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<includes>
<include>**/*IntegrationTest.java</include>
</includes>
</configuration>
</plugin>
4. 通过Jacoco聚合覆盖率
Jacoco(Java代码覆盖率)是用于Java应用程序在测试期间测量代码覆盖率的工具。现在,让我们计算覆盖率报告。
4.1. 准备Jacoco代理
prepare-agent
阶段设置必要的钩子和配置,以便在运行测试时让Jacoco跟踪执行的代码。此配置在运行任何测试之前都是必需的。因此,我们将在父模块的pom.xml
中直接添加准备步骤:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
</executions>
</plugin>
4.2. 收集测试结果
为了收集测试覆盖率,我们将创建一个新的模块aggregate-report
。它仅包含一个pom.xml
,并且依赖于前两个模块。
由于准备阶段,我们可以聚合每个模块的报告。这将是report-aggregate
目标的工作:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>report-aggregate</goal>
</goals>
<configuration>
<dataFileIncludes>
<dataFileInclude>**/jacoco.exec</dataFileInclude>
</dataFileIncludes>
<outputDirectory>${project.reporting.outputDirectory}/jacoco-aggregate</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
我们现在可以从父模块运行verify
目标:
$ mvn clean verify
构建结束时,我们可以在aggregate-report
子模块的target/site/jacoco-aggregate
文件夹中看到Jacoco生成的报告。
打开index.html
文件查看结果。
首先,我们可以导航到控制器类的报告:
正如预期的那样,构造函数和fullyTested()
和indirectlyTestingServiceMethod()
方法被测试覆盖,而notTested()
没有被覆盖。
现在,让我们看看服务类的报告:
这次,让我们关注coveredByIntegrationTest()
方法。我们知道,服务模块中没有测试这个方法的测试。唯一通过这个方法代码的测试是在控制器模块中。然而,Jacoco识别到了对这个方法的测试。在这种情况下,聚合的意义得到了充分展现!
5. 总结
在这篇文章中,我们创建了一个多模块项目,并借助Jacoco收集了测试覆盖率。
请记住,我们需要在运行测试之前运行准备阶段,而在之后进行聚合。要深入了解,我们可以使用工具如SonarQube来获得覆盖率结果的概览。
一如既往,代码可在GitHub上获取。