Java 中的测试:从单元测试到端到端测试
代码质量是衡量开发者专业水平的关键指标之一。在 Java 世界中,测试早已不仅仅是一种良好实践,而是行业标准。没有可靠的测试,就很难维护大型项目、引入新功能以及自信地进行代码重构。在本文中,我们将探讨三个关键的测试层级:unit(单元测试)、integration(集成测试)和 e2e(端到端测试)。您将了解它们的区别、如何在 Java 中正确编写它们以及使用哪些工具。
测试金字塔及其重要性
在深入细节之前,有必要回顾一下 测试金字塔(Mike Cohn)的概念。它直观地展示了不同层级测试的理想比例:
- Unit 测试(金字塔的基底)——速度最快、成本最低、数量最多。它们应占约 70%。
- Integration 测试(中间层)——检查组件之间的交互。约占 20%。
- E2E 测试(金字塔顶端)——速度最慢、成本最高,但对于验证用户场景至关重要。约占 10%。
遵循这一比例,可以在花费最少测试运行时间的情况下,对代码获得最大的信心。
1. Java 中的 Unit 测试
Unit 测试 隔离地测试代码的一个“单元”——通常是一个方法或类。其主要目标是确保方法的逻辑正确运行,而不依赖于外部依赖项(数据库、API、文件系统)。为了实现隔离,会使用 mock 对象。
主要工具
- JUnit 5 —— 用于编写测试的标准框架。
- Mockito —— 用于创建 mock 对象和验证交互的库。
- AssertJ —— 用于 fluent 断言(提高可读性)的库。
使用 JUnit 5 和 Mockito 的 Unit 测试示例
假设我们有一个服务 UserService,它依赖于 UserRepository:
public class UserService { private final UserRepository repository; public UserService(UserRepository repository) { this.repository = repository; } public User findById(Long id) { return repository.findById(id) .orElseThrow(() -> new UserNotFoundException("User not found")); }}针对该方法的测试:
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 java.util.Optional;import static org.junit.jupiter.api.Assertions.*;import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)class UserServiceTest {
@Mock private UserRepository repository;
@InjectMocks private UserService userService;
@Test void testFindById_UserExists() { // Given Long userId = 1L; User expectedUser = new User(userId, "Alice"); when(repository.findById(userId)).thenReturn(Optional.of(expectedUser));
// When User actualUser = userService.findById(userId);
// Then assertNotNull(actualUser); assertEquals("Alice", actualUser.getName()); verify(repository).findById(userId); }
@Test void testFindById_UserNotFound() { // Given Long userId = 99L; when(repository.findById(userId)).thenReturn(Optional.empty());
// When & Then assertThrows(UserNotFoundException.class, () -> userService.findById(userId)); }}最佳实践:
- 测试单一