0


Spring Boot 中应用单元测试(UT):结合 Mock 和 H2 讲解和案例示范

1. 引言

单元测试的目标是验证程序中每个模块的正确性。通过编写单元测试,开发者可以确保功能按预期工作,并在未来的开发中减少引入缺陷的风险。Spring Boot 提供了强大的测试支持,结合 Mock 和 H2 数据库,可以高效地进行测试。

2. Spring Boot 单元测试基础

2.1 什么是单元测试?

单元测试是对程序中最小可测试单元(如方法或类)进行验证的过程。它通常由开发者编写,并使用测试框架(如 JUnit、Mockito)来执行。

2.2 Spring Boot 测试支持

Spring Boot 提供了

  1. spring-boot-starter-test

依赖,该依赖包含了测试所需的常用库,如 JUnit、Mockito 和 AssertJ。可以通过以下 Maven 依赖引入:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-test</artifactId>
  4. <scope>test</scope>
  5. </dependency>

3. 使用 H2 数据库进行测试

H2 数据库是一个轻量级的 Java SQL 数据库,广泛用于单元测试中,尤其是在 Spring Boot 应用中。由于 H2 是内存数据库,它非常适合快速的集成测试和单元测试,因为可以在每次测试前清空数据库,从而确保测试环境的一致性。

3.1 H2 数据库的配置

在 Spring Boot 项目中,配置 H2 数据库通常非常简单。只需在

  1. application.properties

  1. application.yml

文件中添加以下配置:

  1. # application.properties
  2. spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
  3. spring.datasource.driver-class-name=org.h2.Driver
  4. spring.datasource.username=sa
  5. spring.datasource.password=
  6. spring.h2.console.enabled=true
  7. spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

3.2 H2 控制台访问

H2 提供了一个网页控制台,方便开发人员在测试期间查看数据库内容。启用控制台后,访问

  1. http://localhost:8080/h2-console

,输入 JDBC URL(如

  1. jdbc:h2:mem:testdb

)以及相应的用户名和密码,即可登录。

3.3 在测试中使用 H2 数据库

在编写测试时,可以使用

  1. @DataJpaTest

注解来简化设置,这样 Spring Boot 会自动配置 H2 数据库并扫描 JPA 相关组件。

  1. import org.junit.jupiter.api.Test;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.boot.test.autoconfigure.data.jpa.DataJpaTest;
  4. import org.springframework.test.annotation.Rollback;
  5. import static org.assertj.core.api.Assertions.assertThat;
  6. @DataJpaTest
  7. public class UserActivityRepositoryTest {
  8. @Autowired
  9. private UserActivityRepository userActivityRepository;
  10. @Test
  11. @Rollback(false) // 可选,避免测试后清空数据
  12. public void testSaveActivity() {
  13. UserActivity activity = new UserActivity();
  14. activity.setUserId("user1");
  15. activity.setAction("login");
  16. UserActivity savedActivity = userActivityRepository.save(activity);
  17. assertThat(savedActivity.getId()).isNotNull();
  18. assertThat(savedActivity.getUserId()).isEqualTo("user1");
  19. }
  20. }

3.4 处理 MySQL 函数不兼容的场景

尽管 H2 数据库功能强大,但它并不完全兼容 MySQL 的所有特性。在测试中,如果你使用 MySQL 特有的函数或语法,可能会遇到问题。以下是一些常见的兼容性问题及其解决方案。

3.4.1 使用 H2 特性替代 MySQL 函数

对于 MySQL 中常见的函数,可以查阅 H2 文档,寻找相应的替代函数。例如:

  • **MySQL 的 NOW()**:在 H2 中可以使用 CURRENT_TIMESTAMP
  • **MySQL 的 IFNULL(col1, col2)**:在 H2 中使用 COALESCE(col1, col2)
3.4.2 通过
  1. MODE

配置 MySQL 兼容性

H2 提供了一种方式来设置数据库模式,使其更接近 MySQL 的行为。可以通过以下配置设置 MySQL 模式:

  1. spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE

这种模式使 H2 在处理 SQL 语句时遵循 MySQL 的某些行为。例如,它会识别 MySQL 的

  1. AUTO_INCREMENT

关键字。

3.4.3 自定义 SQL 脚本

如果你的应用依赖于特定的 MySQL 函数或语法,可以通过编写 SQL 脚本,在 H2 数据库中创建所需的视图或存储过程。

  1. CREATE ALIAS IF NOT EXISTS `IFNULL` AS $$
  2. public static String ifnull(String str1, String str2) {
  3. return str1 != null ? str1 : str2;
  4. }
  5. $$;

4. Mock 对象的使用

Mock 对象在单元测试中扮演着至关重要的角色。它们用于模拟实际对象的行为,以便我们可以专注于测试特定模块,而无需担心其依赖的外部组件。

4.1 什么是 Mock?

Mock 是对真实对象的模拟,允许开发者定义预期的行为和返回值。通过使用 Mock,开发者可以隔离被测试的单元,从而提高测试的效率和可靠性。

4.2 使用 Mockito 创建 Mock

Mockito 是一个流行的 Java Mock 框架,提供了简单易用的 API。以下是创建和使用 Mock 对象的基本步骤:

  1. 添加依赖:确保在 Maven 中引入 Mockito 依赖(通常已包含在 spring-boot-starter-test 中)。
  1. <dependency>
  2. <groupId>org.mockito</groupId>
  3. <artifactId>mockito-core</artifactId>
  4. <version>3.11.2</version>
  5. <scope>test</scope>
  6. </dependency>
  1. 创建 Mock 对象:使用 @Mock 注解创建 Mock 对象,并使用 @InjectMocks 注解将其注入到被测试的类中。
  1. @Mock
  2. private UserActivityRepository userActivityRepository;
  3. @InjectMocks
  4. private UserActivityService userActivityService;
  1. 初始化 Mock 对象:在测试的 @BeforeEach 方法中使用 MockitoAnnotations.openMocks(this) 来初始化 Mock 对象。
  2. 定义 Mock 行为:使用 when(...).thenReturn(...) 来定义 Mock 对象的行为。例如:
  1. when(userActivityRepository.save(any(UserActivity.class))).thenReturn(activity);
  1. 验证交互:使用 verify(...) 方法来验证 Mock 对象的交互,确保被测试的单元以正确的方式调用了依赖。
  1. verify(userActivityRepository, times(1)).save(activity);

4.3 Mock 对象的优点

  • 解耦:使用 Mock 可以将被测试单元与外部依赖解耦,提高测试的独立性。
  • 可控性:可以控制 Mock 的行为和返回值,以测试不同的场景和边界条件。
  • 简化测试:通过 Mock,可以避免复杂的环境设置和状态管理,简化测试过程。

4.4 示例:使用 Mockito 进行 Mock 测试

下面是一个使用 Mockito 进行 Mock 测试的简单示例:

  1. import static org.mockito.Mockito.*;
  2. import static org.junit.jupiter.api.Assertions.*;
  3. public class UserServiceTest {
  4. @Mock
  5. private UserActivityRepository userActivityRepository;
  6. @InjectMocks
  7. private UserActivityService userActivityService;
  8. @BeforeEach
  9. public void setUp() {
  10. MockitoAnnotations.openMocks(this);
  11. }
  12. @Test
  13. public void testSaveActivity() {
  14. UserActivity activity = new UserActivity();
  15. activity.setId("1");
  16. activity.setUserId("user1");
  17. activity.setAction("login");
  18. when(userActivityRepository.save(activity)).thenReturn(activity);
  19. UserActivity savedActivity = userActivityService.saveActivity(activity);
  20. assertEquals("user1", savedActivity.getUserId());
  21. verify(userActivityRepository, times(1)).save(activity);
  22. }
  23. }

5. 数据分析系统案例

在本节中,我们将以一个简单的数据分析系统为案例,展示如何进行单元测试。

5.1 系统需求分析

数据分析系统需要处理用户行为数据,主要功能包括:

  • 存储用户行为记录。
  • 查询用户行为记录。
  • 分析用户活跃度。

5.2 数据模型设计

我们定义一个

  1. UserActivity

类,表示用户行为数据:

  1. import javax.persistence.Entity;
  2. import javax.persistence.Id;
  3. import java.time.LocalDateTime;
  4. @Entity
  5. public class UserActivity {
  6. @Id
  7. private String id;
  8. private String userId;
  9. private String action;
  10. private LocalDateTime timestamp;
  11. // Getters and Setters
  12. }

5.3 Repository 接口

定义一个

  1. UserActivityRepository

接口用于数据访问:

  1. import org.springframework.data.jpa.repository.JpaRepository;
  2. public interface UserActivityRepository extends JpaRepository<UserActivity, String> {
  3. List<UserActivity> findByUserId(String userId);
  4. }

5.4 服务层实现

在服务层中实现用户行为数据的存储和查询逻辑:

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.stereotype.Service;
  3. import java.util.List;
  4. @Service
  5. public class UserActivityService {
  6. @Autowired
  7. private UserActivityRepository userActivityRepository;
  8. public UserActivity saveActivity(UserActivity activity) {
  9. return userActivityRepository.save(activity);
  10. }
  11. public List<UserActivity> getActivitiesByUserId(String userId) {
  12. return userActivityRepository.findByUserId(userId);
  13. }
  14. }

5.5 控制器实现

创建一个 REST 控制器以提供 API 接口:

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.web.bind.annotation.*;
  3. import java.util.List;
  4. @RestController
  5. @RequestMapping("/api/activities")
  6. public class UserActivityController {
  7. @Autowired
  8. private UserActivityService userActivityService;
  9. @PostMapping
  10. public UserActivity createActivity(@RequestBody UserActivity activity) {
  11. return userActivityService.saveActivity(activity);
  12. }
  13. @GetMapping("/{userId}")
  14. public List<UserActivity> getActivities(@PathVariable String userId) {
  15. return userActivityService.getActivitiesByUserId(userId);
  16. }
  17. }

5.6 单元测试实现

现在,我们开始为上述服务和控制器编写单元测试。

5.6.1 服务层单元测试
  1. import static org.mockito.Mockito.*;
  2. import static org.junit.jupiter.api.Assertions.*;
  3. import org.junit.jupiter.api.BeforeEach;
  4. import org.junit.jupiter.api.Test;
  5. import org.mockito.InjectMocks;
  6. import org.mockito.Mock;
  7. import org.mockito.MockitoAnnotations;
  8. import java.util.Arrays;
  9. import java.util.List;
  10. public class UserActivityServiceTest {
  11. @Mock
  12. private UserActivityRepository userActivityRepository;
  13. @InjectMocks
  14. private UserActivityService userActivityService;
  15. @BeforeEach
  16. public void setUp() {
  17. MockitoAnnotations.openMocks(this);
  18. }
  19. @Test
  20. public void testSaveActivity() {
  21. UserActivity activity = new UserActivity();
  22. activity.setId("1");
  23. activity.setUserId("user1");
  24. activity.setAction("login");
  25. when(userActivityRepository.save(activity)).thenReturn(activity);
  26. UserActivity savedActivity = userActivityService.saveActivity(activity);
  27. assertEquals("user1", savedActivity.getUserId());
  28. verify(userActivityRepository, times(1)).save(activity);
  29. }
  30. @Test
  31. public void testGetActivitiesByUserId() {
  32. UserActivity activity1 = new UserActivity();
  33. activity1.setUserId("user1");
  34. UserActivity activity2 = new UserActivity();
  35. activity2.setUserId("user1");
  36. when(userActivityRepository.findByUserId("user1"))
  37. .thenReturn(Arrays.asList(activity1, activity2));
  38. List<UserActivity> activities = userActivityService.getActivitiesByUserId("user1");
  39. assertEquals(2, activities.size());
  40. verify(userActivityRepository, times(1)).findByUserId("user1");
  41. }
  42. }
5.6.2 控制器层单元测试

在控制器层的单元测试中,我们需要确保 REST API 的正确性。通过模拟服务层的行为,我们可以测试控制器对请求的处理是否正确。

控制器层单元测试示例

  1. import static org.mockito.Mockito.*;
  2. import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
  3. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
  4. import org.junit.jupiter.api.BeforeEach;
  5. import org.junit.jupiter.api.Test;
  6. import org.mockito.InjectMocks;
  7. import org.mockito.Mock;
  8. import org.mockito.MockitoAnnotations;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.http.MediaType;
  11. import org.springframework.test.web.servlet.MockMvc;
  12. import org.springframework.test.web.servlet.setup.MockMvcBuilders;
  13. import java.util.Arrays;
  14. public class UserActivityControllerTest {
  15. @Autowired
  16. private MockMvc mockMvc;
  17. @Mock
  18. private UserActivityService userActivityService;
  19. @InjectMocks
  20. private UserActivityController userActivityController;
  21. @BeforeEach
  22. public void setUp() {
  23. MockitoAnnotations.openMocks(this);
  24. mockMvc = MockMvcBuilders.standaloneSetup(userActivityController).build();
  25. }
  26. @Test
  27. public void testCreateActivity() throws Exception {
  28. UserActivity activity = new UserActivity();
  29. activity.setId("1");
  30. activity.setUserId("user1");
  31. activity.setAction("login");
  32. when(userActivityService.saveActivity(any(UserActivity.class))).thenReturn(activity);
  33. mockMvc.perform(post("/api/activities")
  34. .contentType(MediaType.APPLICATION_JSON)
  35. .content("{\"userId\":\"user1\", \"action\":\"login\"}"))
  36. .andExpect(status().isOk())
  37. .andExpect(jsonPath("$.userId").value("user1"))
  38. .andExpect(jsonPath("$.action").value("login"));
  39. }
  40. @Test
  41. public void testGetActivities() throws Exception {
  42. UserActivity activity1 = new UserActivity();
  43. activity1.setUserId("user1");
  44. activity1.setAction("login");
  45. when(userActivityService.getActivitiesByUserId("user1")).thenReturn(Arrays.asList(activity1));
  46. mockMvc.perform(get("/api/activities/user1"))
  47. .andExpect(status().isOk())
  48. .andExpect(jsonPath("$[0].userId").value("user1"))
  49. .andExpect(jsonPath("$[0].action").value("login"));
  50. }
  51. }

解释

  1. **MockitoAnnotations.openMocks(this)**:初始化 Mock 对象,允许在测试中使用 @Mock 和 @InjectMocks 注解。
  2. **MockMvcBuilders.standaloneSetup(userActivityController)**:创建一个独立的 MockMvc 实例,用于测试控制器。
  3. **when(…).thenReturn(…)**:定义 Mock 对象的行为,当调用特定方法时返回指定的值。
  4. **mockMvc.perform(…)**:模拟 HTTP 请求,并验证返回的状态和内容。

通过上述测试,确保了控制器层对 API 的正确处理,包括创建活动和获取活动的功能。


本文转载自: https://blog.csdn.net/weixin_39996520/article/details/143246205
版权归原作者 J老熊 所有, 如有侵权,请联系我们删除。

“Spring Boot 中应用单元测试(UT):结合 Mock 和 H2 讲解和案例示范”的评论:

还没有评论