锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. Spring Boot中如何编写优雅的单元测试

Spring Boot中如何编写优雅的单元测试

0
  • 软件开发
  • 发布于 2024-08-16
  • 0 次阅读
黄健
黄健

本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net

单元测试是指对软件中的最小可测试单元进行检查和验证。在 Java 中,单元测试的最小单元是类。通过编写针对类或方法的小段代码,来检验被测代码是否符合预期结果或行为。执行单元测试可以帮助开发者验证代码是否正确实现了功能需求,以及是否能够适应应用环境或需求变化。

本文将介绍如何在 Spring Boot 中编写优雅的单元测试,包括如何添加单元测试依赖,如何对不同层次的组件进行单元测试,以及如何使用 Mock 对象来模拟真实对象行为。本文假设读者已经对 Spring Boot 和单元测试有一定的了解和基础。

目录

一、Spring Boot 中的单元测试依赖

二、Spring Boot 中不同层次的单元测试

service 层

Controller 层

Repository 层

三、Spring Boot 中 Mock 对象的使用

总结

一、Spring Boot 中的单元测试依赖

在 Spring Boot 项目中,要进行单元测试,首先需要添加相应的依赖。如果使用 Maven 作为构建工具,可以在 pom.xml 文件中添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

这个依赖包含了多个库和功能,主要有以下几个:

  • JUnit:JUnit 是 Java 中最流行和最常用的单元测试框架,它提供了一套注解和断言来编写和运行单元测试。例如 @Test 注解表示一个测试方法,assertEquals 断言表示两个值是否相等。
  • Spring Test:Spring Test 是一个基于 Spring 的测试框架,它提供了一套注解和工具来配置和管理 Spring 上下文和 Bean。例如 @SpringBootTest 注解表示一个集成测试类,@Autowired 注解表示自动注入一个 Bean。
  • Mockito:Mockito 是一个 Java 中最流行和最强大的 Mock 对象库,它可以模拟复杂的真实对象行为,从而简化测试过程。例如 @MockBean 注解表示创建一个 Mock 对象,when 方法表示定义 Mock 对象的行为。
  • Hamcrest:Hamcrest 是一个 Java 中的匹配器库,它提供了一套语义丰富而易读的匹配器来进行结果验证。例如 assertThat 断言表示验证一个值是否满足一个匹配器,is 匹配器表示两个值是否相等。
  • AssertJ:AssertJ 是一个 Java 中的断言库,它提供了一套流畅而直观的断言语法来进行结果验证。例如 assertThat 断言表示验证一个值是否满足一个条件,isEqualTo 断言表示两个值是否相等。

除了以上这些库外,spring-boot-starter-test 还包含了其他一些库和功能,如 JsonPath、JsonAssert、XmlUnit 等。这些库和功能可以根据不同的测试场景进行选择和使用。

 二、Spring Boot 中不同层次的单元测试

service 层

Service 层是指封装业务逻辑和处理数据的层,它通常使用 @Service 或 @Component 注解来标识。在 Spring Boot 中,对 Service 层进行单元测试,可以使用 @SpringBootTest 注解来加载完整的 Spring 上下文,从而可以自动注入 Service 层的 Bean。同时,可以使用 @MockBean 注解来创建和注入其他层次的 Mock 对象,从而避免真实地调用其他层次的方法,而是模拟其行为。

例如,假设有一个 UserService 类,它提供了一个根据用户 ID 查询用户信息的方法:

@Service
public class UserService {
 
    @Autowired
    private UserRepository userRepository;
 
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

要对这个类进行单元测试,可以编写以下测试类:

@SpringBootTest
public class UserServiceTest {
 
    @Autowired
    private UserService userService;
 
    @MockBean
    private UserRepository userRepository;
 
    @Test
    public void testGetUserById() {
        // 创建一个User对象
        User user = new User();
        user.setId(1L);
        user.setName("Alice");
        user.setEmail("alice@example.com");
 
        // 当调用userRepository.findById(1L)时,返回一个包含user对象的Optional对象
        when(userRepository.findById(1L)).thenReturn(Optional.of(user));
 
        // 调用userService.getUserById方法,传入1L作为参数,得到一个User对象
        User result = userService.getUserById(1L);
 
        // 验证结果对象与user对象相等
        assertThat(result).isEqualTo(user);
 
        // 验证userRepository.findById(1L)方法被调用了一次
        verify(userRepository, times(1)).findById(1L);
    }
}

在这个测试类中,使用了以下几个关键点和技巧:

  • 使用 @SpringBootTest 注解表示加载完整的 Spring 上下文,并使用 @Autowired 注解将 UserService 对象注入到测试类中。
  • 使用 @MockBean 注解表示创建一个 UserRepository 的 Mock 对象,并使用 @Autowired 注解将其注入到测试类中。这样可以避免真实地调用 UserRepository 的方法,而是模拟其行为。
  • 使用 when 方法来定义 Mock 对象的行为,例如当调用 userRepository.findById(1L) 时,返回一个包含 user 对象的 Optional 对象。
  • 使用 userService.getUserById 方法调用被测方法,得到一个 User 对象。
  • 使用 AssertJ 的断言语法来验证结果对象与 user 对象是否相等。可以使用多种条件和匹配器来验证结果。
  • 使用 verify 方法来验证 Mock 对象的方法是否被调用了指定次数。

Controller 层

Controller 层是指处理用户请求和响应的层,它通常使用 @RestController 或 @Controller 注解来标识。在 Spring Boot 中,对 Controller 层进行单元测试,可以使用 @WebMvcTest 注解来启动一个轻量级的 Spring MVC 上下文,只加载 Controller 层的组件。同时,可以使用 @AutoConfigureMockMvc 注解来自动配置一个 MockMvc 对象,用来模拟 Http 请求和验证 Http 响应。

例如,假设有一个 UserController 类,它提供了一个根据用户 ID 查询用户信息的接口:

@RestController
@RequestMapping("/users")
public class UserController {
 
    @Autowired
    private UserService userService;
 
    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        if (user == null) {
            return ResponseEntity.notFound().build();
        } else {
            return ResponseEntity.ok(user);
        }
    }
}

要对这个类进行单元测试,可以编写以下测试类:

@WebMvcTest(UserController.class)
@AutoConfigureMockMvc
public class UserControllerTest {
 
    @Autowired
    private MockMvc mockMvc;
 
    @MockBean
    private UserService userService;
 
    @Test
    public void testGetUserById() throws Exception {
        // 创建一个User对象
        User user = new User();
        user.setId(1L);
        user.setName("Alice");
        user.setEmail("alice@example.com");
 
        // 当调用userService.getUserById(1L)时,返回user对象
        when(userService.getUserById(1L)).thenReturn(user);
 
        // 模拟发送GET请求到/users/1,并验证响应状态码为200,响应内容为JSON格式的user对象
        mockMvc.perform(get("/users/1"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.id").value(1L))
                .andExpect(jsonPath("$.name").value("Alice"))
                .andExpect(jsonPath("$.email").value("alice@example.com"));
 
        // 验证userService.getUserById(1L)方法被调用了一次
        verify(userService, times(1)).getUserById(1L);
    }
}

在这个测试类中,使用了以下几个关键点和技巧:

  • 使用 @WebMvcTest(UserController.class) 注解表示只加载 UserController 类的组件,不加载其他层次的组件。
  • 使用 @AutoConfigureMockMvc 注解表示自动配置一个 MockMvc 对象,并使用 @Autowired 注解将其注入到测试类中。
  • 使用 @MockBean 注解表示创建一个 UserService 的 Mock 对象,并使用 @Autowired 注解将其注入到测试类中。这样可以避免真实地调用 UserService 的方法,而是模拟其行为。
  • 使用 when 方法来定义 Mock 对象的行为,例如当调用 userService.getUserById(1L) 时,返回 user 对象。
  • 使用 mockMvc.perform 方法来模拟发送 Http 请求,并使用 andExpect 方法来验证 Http 响应。可以使用多种匹配器来验证响应状态码、内容类型、内容值等。
  • 使用 verify 方法来验证 Mock 对象的方法是否被调用了指定次数。

Repository 层

Repository 层是指封装数据访问和持久化的层,它通常使用 @Repository 或 @JpaRepository 注解来标识。在 Spring Boot 中,对 Repository 层进行单元测试,可以使用 @DataJpaTest 注解来启动一个嵌入式数据库,并自动配置 JPA 相关的组件。同时,可以使用 @TestEntityManager 注解来获取一个 TestEntityManager 对象,用来操作和验证数据库数据。

例如,假设有一个 UserRepository 接口,它继承了 JpaRepository 接口,并提供了一个根据用户姓名查询用户列表的方法:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
 
    List<User> findByName(String name);
}

要对这个接口进行单元测试,可以编写以下测试类:

@DataJpaTest
public class UserRepositoryTest {
 
    @Autowired
    private UserRepository userRepository;
 
    @Autowired
    private TestEntityManager testEntityManager;
 
    @Test
    public void testFindByName() {
        // 创建两个User对象,并使用testEntityManager.persist方法将其保存到数据库中
        User user1 = new User();
        user1.setName("Bob");
        user1.setEmail("bob@example.com");
        testEntityManager.persist(user1);
 
        User user2 = new User();
        user2.setName("Bob");
        user2.setEmail("bob2@example.com");
        testEntityManager.persist(user2);
 
        // 调用userRepository.findByName方法,传入"Bob"作为参数,得到一个用户列表
        List<User> users = userRepository.findByName("Bob");
 
        // 验证用户列表的大小为2,且包含了user1和user2
        assertThat(users).hasSize(2);
        assertThat(users).contains(user1, user2);
    }
}

在这个测试类中,使用了以下几个关键点和技巧:

  • 使用 @DataJpaTest 注解表示启动一个嵌入式数据库,并自动配置 JPA 相关的组件。这样可以避免依赖外部数据库,而是使用内存数据库进行测试。
  • 使用 @Autowired 注解将 UserRepository 和 TestEntityManager 对象注入到测试类中。
  • 使用 testEntityManager.persist 方法将 User 对象保存到数据库中。这样可以准备好测试数据,而不需要手动插入数据。
  • 使用 userRepository.findByName 方法调用自定义的查询方法,得到一个用户列表。
  • 使用 AssertJ 的断言语法来验证用户列表的大小和内容。可以使用多种条件和匹配器来验证结果。

三、Spring Boot 中 Mock 对象的使用

在 Spring Boot 中,除了使用 @WebMvcTest 和 @DataJpaTest 等注解来加载特定层次的组件外,还可以使用 @SpringBootTest 注解来加载完整的 Spring 上下文,从而进行更加集成的测试。但是,在这种情况下,可能会遇到一些问题,例如:

        - 测试过程中需要依赖外部资源,如数据库、消息队列、Web 服务等。这些资源可能不稳定或不可用,导致测试失败或超时。
- 测试过程中需要调用其他组件或服务的方法,但是这些方法的实现或行为不确定或不可控,导致测试结果不可预测或不准确。
- 测试过程中需要验证一些难以观察或测量的结果,如日志输出、异常抛出、私有变量值等。这些结果可能需要使用复杂或侵入式的方式来获取或验证。

为了解决这些问题,可以使用 Mock 对象来模拟真实对象行为。Mock 对象是指在测试过程中替代真实对象的虚拟对象,它可以根据预设的规则来返回特定的值或执行特定的操作。使用 Mock 对象有以下好处:

        - 降低测试依赖:通过使用 Mock 对象来替代外部资源或其他组件,可以减少测试过程中对真实环境的依赖,使得测试更加稳定和可靠。
- 提高测试控制:通过使用 Mock 对象来模拟特定的行为或场景,可以提高测试过程中对真实对象行为的控制,使得测试更加灵活和精确。
- 简化测试验证:通过使用 Mock 对象来返回特定的结果或触发特定的事件,可以简化测试过程中对真实对象结果或事件的验证,使得测试更加简单和直观。

在 Spring Boot 中,要使用 Mock 对象,可以使用 @MockBean 注解来创建和注入一个 Mock 对象。这个注解会自动使用 Mockito 库来创建一个 Mock 对象,并将其添加到 Spring 上下文中。同时,可以使用 when 方法来定义 Mock 对象的行为,以及 verify 方法来验证 Mock 对象的方法调用。

例如,假设有一个 EmailService 接口,它提供了一个发送邮件的方法:

public interface EmailService {
 
    void sendEmail(String to, String subject, String content);
}

要对这个接口进行单元测试,可以编写以下测试类:

@SpringBootTest
public class EmailServiceTest {
 
    @Autowired
    private UserService userService;
 
    @MockBean
    private EmailService emailService;
 
    @Test
    public void testSendEmail() {
        // 创建一个User对象
        User user = new User();
        user.setId(1L);
        user.setName("Alice");
        user.setEmail("alice@example.com");
 
        // 当调用emailService.sendEmail方法时,什么也不做
        doNothing().when(emailService).sendEmail(anyString(), anyString(), anyString());
 
        // 调用userService.sendWelcomeEmail方法,传入user对象作为参数
        userService.sendWelcomeEmail(user);
 
        // 验证emailService.sendEmail方法被调用了一次,并且参数分别为user.getEmail()、"Welcome"、"Hello, Alice"
        verify(emailService, times(1)).sendEmail(user.getEmail(), "Welcome", "Hello, Alice");
    }
}

在这个测试类中,使用了以下几个关键点和技巧:

  • 使用 @SpringBootTest 注解表示加载完整的 Spring 上下文,并使用 @Autowired 注解将 UserService 对象注入到测试类中。
  • 使用 @MockBean 注解表示创建一个 EmailService 的 Mock 对象,并使用 @Autowired 注解将其注入到测试类中。这样可以避免真实地调用 EmailService 的方法,而是模拟其行为。
  • 使用 doNothing 方法来定义 Mock 对象的行为,例如当调用 emailService.sendEmail 方法时,什么也不做。也可以使用 doReturn、doThrow、doAnswer 等方法来定义其他类型的行为。
  • 使用 anyString 方法来表示任意字符串类型的参数。也可以使用 anyInt、anyLong、anyObject 等方法来表示其他类型的参数。
  • 使用 userService.sendWelcomeEmail 方法调用被测方法,传入 user 对象作为参数。
  • 使用 verify 方法来验证 Mock 对象的方法是否被调用了指定次数,并且参数是否符合预期。也可以使用 never、atLeast、atMost 等方法来表示其他次数的验证。

除了使用 @MockBean 注解来创建和注入 Mock 对象外,还可以使用 @SpyBean 注解来创建和注入 Spy 对象。Spy 对象是指在测试过程中部分替代真实对象的虚拟对象,它可以根据预设的规则来返回特定的值或执行特定的操作,同时保留真实对象的其他行为。使用 Spy 对象有以下好处:

  • 保留真实行为:通过使用 Spy 对象来替代真实对象,可以保留真实对象的其他行为,使得测试更加接近真实环境。
  • 修改部分行为:通过使用 Spy 对象来模拟特定的行为或场景,可以修改真实对象的部分行为,使得测试更加灵活和精确。
  • 观察真实结果:通过使用 Spy 对象来返回特定的结果或触发特定的事件,可以观察真实对象的结果或事件,使得测试更加直观和可信。

在 Spring Boot 中,要使用 Spy 对象,可以使用 @SpyBean 注解来创建和注入一个 Spy 对象。这个注解会自动使用 Mockito 库来创建一个 Spy 对象,并将其添加到 Spring 上下文中。同时,可以使用 when 方法来定义 Spy 对象的行为,以及 verify 方法来验证 Spy 对象的方法调用。

例如,假设有一个 LogService 接口,它提供了一个记录日志的方法:

public interface LogService {
 
    void log(String message);
}

要对这个接口进行单元测试,可以编写以下测试类:

@SpringBootTest
public class LogServiceTest {
 
    @Autowired
    private UserService userService;
 
    @SpyBean
    private LogService logService;
 
    @Test
    public void testLog() {
        // 创建一个User对象
        User user = new User();
        user.setId(1L);
        user.setName("Alice");
        user.setEmail("alice@example.com");
 
        // 当调用logService.log方法时,调用真实的方法,并打印参数到控制台
        doAnswer(invocation -> {
            String message = invocation.getArgument(0);
            System.out.println(message);
            invocation.callRealMethod();
            return null;
        }).when(logService).log(anyString());
 
        // 调用userService.createUser方法,传入user对象作为参数
        userService.createUser(user);
 
        // 验证logService.log方法被调用了两次,并且参数分别为"Creating user: Alice"、"User created: Alice"
        verify(logService, times(2)).log(anyString());
        verify(logService, times(1)).log("Creating user: Alice");
        verify(logService, times(1)).log("User created: Alice");
    }
}

在这个测试类中,使用了以下几个关键点和技巧:

  • 使用 @SpringBootTest 注解表示加载完整的 Spring 上下文,并使用 @Autowired 注解将 UserService 对象注入到测试类中。
  • 使用 @SpyBean 注解表示创建一个 LogService 的 Spy 对象,并使用 @Autowired 注解将其注入到测试类中。这样可以保留 LogService 的真实行为,同时修改部分行为。
  • 使用 doAnswer 方法来定义 Spy 对象的行为,例如当调用 logService.log 方法时,调用真实的方法,并打印参数到控制台。也可以使用 doReturn、doThrow、doNothing 等方法来定义其他类型的行为。
  • 使用 anyString 方法来表示任意字符串类型的参数。也可以使用 anyInt、anyLong、anyObject 等方法来表示其他类型的参数。
  • 使用 userService.createUser 方法调用被测方法,传入 user 对象作为参数。
  • 使用 verify 方法来验证 Spy 对象的方法是否被调用了指定次数,并且参数是否符合预期。也可以使用 never、atLeast、atMost 等方法来表示其他次数的验证。

总结

本文介绍了如何在 Spring Boot 中编写优雅的单元测试,包括如何添加单元测试依赖,如何对不同层次的组件进行单元测试,以及如何使用 Mock 对象和 Spy 对象来模拟真实对象行为。本文还给出了每种类型的单元测试的示例代码,并解释了其中的关键点和技巧。

通过编写单元测试,可以提高 Spring Boot 应用的质量和稳定性,同时也可以提高开发者的编程水平和信心。希望本文能够对你有所帮助和启发,让你能够在 Spring Boot 中编写优雅的单元测试。

标签: #Spring Boot 173 #JAVA 991 #软件开发 1171
相关文章

万字:支付“核心系统”详解 2024-11-02 15:33

专栏作者:隐墨星辰 \| 主编:陈天宇宙 这篇文章也尝试化繁为简,探寻支付系统的本质,讲清楚在线支付系统最核心的一些概念和设计理念。 虽然支付行业已经过了风头最劲的时光,但跨境支付仍然在蓬勃发展,每年依然有很多新人进入这个行业,这篇文章尝试为这些刚入行的新人提供一点帮助。 文章只介绍一些支付行业十几

资深支付架构师视角:实战从问题定义到代码落地的完整套路 2024-11-02 15:33

前言 今天从一个实际案例入手,介绍站在架构师的角度,如何识别并定义问题,提炼需求,技术方案选型,再到详细设计,最后利用AI的能力协助写出核心的代码,验证与调优。 解决问题存在一定的模式,也可以称之为框架,总结出自己的思考和解题框架,以后再碰到同类型的问题就可以如庖丁解牛一样容易。 很多年前,我写代码

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

设计模式第16讲——迭代器模式(Iterator) 2024-10-08 11:24

一、什么是迭代器模式 迭代器模式是一种行为型设计模式,它提供了一种统一的方式来访问集合对象中的元素,而不是暴露集合内部的表示方式。简单地说,就是将遍历集合的责任封装到一个单独的对象中,我们可以按照特定的方式访问集合中的元素。 二、角色组成 抽象迭代器(Iterator):定义了遍历聚合对象所需的方法

vue2路由和vue3路由区别及原理 2024-10-08 11:24

一、Vue2 与 Vue3 路由的区别 1. 创建路由实例方式的不同 Vue 2 中,通过 Vue.use() 注册路由插件,并通过 new VueRouter() 来创建路由实例。 import Vue from 'vue';import VueRouter from 'vue-router';i

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.