前端视角 Java Web 入门手册 5.10:真实世界 Web 开发—— 单元测试

JUnit 是 Java 中最流行的单元测试框架,Mockito 是一个流行的 mocking 框架,用于模拟和验证类的依赖关系,特别适用于单元测试中隔离被测试类的外部依赖。

简单的单元测试

添加依赖

pom.xml 文件中添加以下依赖

xml 复制代码
<dependencies>
    <!-- JUnit 5 测试运行器 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.8.2</version>
        <scope>test</scope>
    </dependency>
    <!-- JUnit 5 断言库 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.8.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

创建待测试类

假设我们有一个简单的计算器类 Calculator

java 复制代码
public class Calculator {
    public int add(int num1, int num2) {
        return num1 + num2;
    }

    public int subtract(int num1, int num2) {
        return num1 - num2;
    }

    public int multiply(int num1, int num2) {
        return num1 * num2;
    }

    public double divide(int num1, int num2) {
        if (num2 == 0) {
            throw new IllegalArgumentException("除数不能为 0");
        }
        return (double) num1 / num2;
    }
}

创建测试类

src/test/java 目录下创建测试类 CalculatorTest

java 复制代码
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class CalculatorTest {

    private Calculator calculator = new Calculator();

    // 测试加法方法
    @Test
    public void testAdd() {
        int result = calculator.add(3, 5);
        assertEquals(8, result, "加法运算结果不正确");
    }

    // 测试减法方法
    @Test
    public void testSubtract() {
        int result = calculator.subtract(10, 4);
        assertEquals(6, result, "减法运算结果不正确");
    }

    // 测试乘法方法
    @Test
    public void testMultiply() {
        int result = calculator.multiply(6, 7);
        assertEquals(42, result, "乘法运算结果不正确");
    }

    // 测试除法方法
    @Test
    public void testDivide() {
        double result = calculator.divide(15, 3);
        assertEquals(5.0, result, "除法运算结果不正确");

        // 测试除数为 0 的情况
        assertThrows(IllegalArgumentException.class, () -> calculator.divide(10, 0));
    }
}

运行测试

在 IDE中可以直接右键点击测试类或测试方法,选择运行测试。也可以通过命令行运行测试,在项目根目录下执行 mvn test 命令

Spring Boot 项目单元测试

添加依赖

Spring Boot 项目可以添加<font style="color:rgb(13, 18, 57);">spring-boot-starter-test</font>,内部包含多个测试工具

依赖名称 说明
JUnit Jupiter 支持 JUnit 5,用于编写和运行测试。
Spring Test 提供Spring的测试支持,如@SpringBootTest
Mockito 用于创建和管理模拟对象,支持依赖注入的测试。
AssertJ 提供流畅且丰富的断言方法。
Hamcrest 另一种断言库,常用于匹配和验证。
JSONassert 用于JSON内容的断言。
JsonPath 用于解析和验证JSON数据。
xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

简单服务测试

假设存在一个简单的服务类 UserService

java 复制代码
package com.example.demo.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public String getUserNameById(Long id) {
        if (id == 1L) {
            return "John";
        }
        return null;
    }
}

对应的单元测试类 UserServiceTest 如下,直接创建 UserService 实例并调用其方法,使用 assertEquals 方法验证结果

java 复制代码
package com.example.demo.service;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

class UserServiceTest {

    private UserService userService = new UserService();

    @Test
    void testGetUserNameById() {
        String result = userService.getUserNameById(1L);
        assertEquals("John", result);
    }
}

带有依赖注入的服务测试

若 UserService 依赖其它组件,例如 UserRepository

java 复制代码
package com.example.demo.service;

import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public String getUserNameById(Long id) {
        return userRepository.findUserNameById(id);
    }
}

这时候需要使用 Mockito 模拟 UserRepository 进行单元测试

  • @ExtendWith启用Mockito扩展,支持注解初始化
  • @Mock 注解模拟 UserRepository
  • @InjectMocks 注解将模拟对象注入到 UserService 中
  • when 方法定义模拟对象的行为
java 复制代码
package com.example.demo.service;

import com.example.demo.repository.UserRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    void testGetUserNameById() {
        when(userRepository.findUserNameById(1L)).thenReturn("John");
        String result = userService.getUserNameById(1L);
        assertEquals("John", result);
    }
}

控制器测试

假设存在一个控制器 UserController

java 复制代码
package com.example.demo.controller;

import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/users/{id}")
    public String getUserName(@PathVariable Long id) {
        return userService.getUserNameById(id);
    }
}

这时候可以使用 Spring MVC Test 框架进行控制器单元测试

  • @WebMvcTest 注解用于测试控制器
  • MockMvc 模拟 HTTP 请求
  • @MockBean 模拟依赖的服务
java 复制代码
package com.example.demo.controller;

import com.example.demo.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void testGetUserName() throws Exception {
        when(userService.getUserNameById(1L)).thenReturn("John");
        mockMvc.perform(get("/users/1"))
               .andExpect(status().isOk())
               .andExpect(content().string("John"));
    }
}

测试报告

Surefire

Maven 提供了 Surefire Report Plugin,默认情况下当运行mvn test时,Maven 会自动在 target/surefire-reports 目录下生成测试报告文件

这些报告文件通常是 XML 格式的,可以使用文本编辑器打开查看详细的测试结果,包括测试用例的执行情况、通过或失败的数量等信息,不过,XML 格式的报告阅读起来不太直观

JaCoCo

JaCoCo 是一个开源的 Java 代码覆盖率工具,在 Spring Boot 项目中使用 JaCoCo 可以生成详细的测试覆盖率报告。

xml 复制代码
<build>
    <plugins>
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>0.8.12</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>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

测试完成后,JaCoCo 会在 target/site/jacoco 目录下生成 HTML 格式的测试覆盖率报告。打开 index.html 文件,即可在浏览器中查看详细的报告内容,包括整体的测试覆盖率、各个类和方法的覆盖率情况等

当然也可以在 CI/CD流程中集成 JaCoCo,帮助开发者在自动化流程中监控代码的测试覆盖率

相关推荐
摸鱼仙人~41 分钟前
Spring Boot中的this::语法糖详解
windows·spring boot·python
Warren981 小时前
Java Stream流的使用
java·开发语言·windows·spring boot·后端·python·硬件工程
程序视点2 小时前
IObit Uninstaller Pro专业卸载,免激活版本,卸载清理注册表,彻底告别软件残留
前端·windows·后端
xidianhuihui2 小时前
go install报错: should be v0 or v1, not v2问题解决
开发语言·后端·golang
架构师沉默2 小时前
Java优雅使用Spring Boot+MQTT推送与订阅
java·开发语言·spring boot
tuokuac2 小时前
MyBatis 与 Spring Boot版本匹配问题
java·spring boot·mybatis
zhysunny3 小时前
05.原型模式:从影分身术到细胞分裂的编程艺术
java·原型模式
进击的铁甲小宝3 小时前
Django-environ 入门教程
后端·python·django·django-environ
草履虫建模3 小时前
RuoYi-Vue 项目 Docker 容器化部署 + DockerHub 上传全流程
java·前端·javascript·vue.js·spring boot·docker·dockerhub
皮皮林5514 小时前
强烈建议你不要再使用Date类了!!!
java