No one can guarantee their code is foolproof unless they have 100% execution coverage — not Lu Xun
In project development, we expect to be able to unit test interfaces and code, and require code coverage of more than 80%. The idea is that if someone else changes the code later, and it affects other systems, we can expose the problem when the code is unit tested. So we expect developers running unit tests locally to see how much coverage their code has, and to show exactly where and what is not covered. This article describes using the Jacoco plug-in to generate unit test coverage reports and view coverage details.
1. Create a Maven project
<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>
<groupId>org.example</groupId>
<artifactId>JacocoDemo</artifactId>
<version>1.0 the SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<jacoco.version>0.8.6</jacoco.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>Against 2.4.1</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<! -- junit 5 -->
<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.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<! -- jacoco: generate test coverage report -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>generate-code-coverage-report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Copy the code
We used Junit5 and created a web project to test it, which I noted in the pom.
2. Create a SpringBoot boot class
org.example.jacocodemo.App
:
package org.example.jacocodemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
public static void main(String[] args) { SpringApplication.run(App.class, args); }}Copy the code
3. Create a Controller:
DemoController:
package org.example.jacocodemo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(path = "/test")
public class DemoController {
@GetMapping(path = "/{branchId}")
public String getBranch(@PathVariable("branchId") int branchId) {
if (branchId == 0) {
return "master";
} else if (branchId == 1) {
return "dev";
}
return "release"; }}Copy the code
The idea here is very simple. We divide code branches through the branchId passed to us, and achieve different code coverage by designing different test cases.
4. Create unit tests
DemoControllerTest
:
package org.example.jacocodemo;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DemoControllerTest {
static MockMvc mockMvc;
@BeforeAll
static void setUp(a) {
mockMvc = MockMvcBuilders.standaloneSetup(new DemoController()).build();
}
@Test
void testGetBranch(a) {
Map<Integer, String> testCases = new HashMap<>();
testCases.put(0."master");
testCases.put(1."dev");
testCases.put(3."release");
testCases.forEach((key, value) -> {
try {
mockMvc
.perform(MockMvcRequestBuilders.get("/test/{branchId}". key) .accept(MediaType.APPLICATION_JSON) .characterEncoding(StandardCharsets.UTF_8.name())) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().string(value)) .andReturn(); }catch(Exception e) { e.printStackTrace(); }}); }}Copy the code
The syntax of junit5 is used, the SpringBootTest and ExtendWith annotations are used to launch unit tests, the use of random ports is specified, and the Controller calls are simulated via MockMvc. Initialization of MockMvc is done in setUp, using a Map to simulate test cases and expected results.
5. Run unit tests
Execute in the project root directorymvn clean test
To execute the unit tests and generate the coverage report, which is./target/site/jacoco/index.html
Open the file in a browser to see the overall coverage and unit test status:Click on our DemoController hierarchy to see code coverage and branch coverage. Click into the file and select method to see which lines of code are executed in the method:
Since SpringBoot uses junit4 by default, the Mvn-surfire-maven-plugin does not execute my unit test when I run the MVN Clean test in junit4. After checking some solutions, they did not take effect, and finally changed to junit5. You can also handle and modify according to the technology used in your own projects. If you have a solution to the problem of unit tests not executing under junit4, please leave a comment.
reference
- How to make Maven unit test code coverage work
- Spring Boot learning notes (5) – write unit tests with JUnit5