引言:为什么 Java 需要测试?
每位 Java 开发者都至少经历过一次这样的情况:修改了一行代码后,整个系统就“崩溃”了。为了避免此类灾难,业界建立了一套完善的测试体系。在本文中,我们将探讨 Java 中的三个测试层级:unit(单元测试)、integration(集成测试)和 e2e(端到端测试)。您将了解它们的区别、应使用哪些工具以及如何正确地编写 Java 测试。
无论您是在编写基于 Spring Boot 的微服务还是桌面应用程序,理解测试金字塔都是一项必备技能。让我们开始吧!
1. 单元测试:质量的基础
单元测试(模块测试)在隔离外部依赖的情况下,验证单个类或方法的功能。这是最快、成本最低的测试层级。
1.1 主要工具
- JUnit 5 — Java 的标准测试框架(最新版本为 Jupiter)。
- Mockito — 用于创建 mock 对象(存根)的库。
- AssertJ — 提供丰富流畅断言方法的库。
1.2 使用 JUnit 5 和 Mockito 的单元测试示例
假设我们有一个依赖于 UserRepository 仓库的 UserService 服务:
public class UserService { private final UserRepository repository;
public UserService(UserRepository repository) { this.repository = repository; }
public String getUserName(Long id) { User user = repository.findById(id); return user != null ? user.getName() : "Unknown"; }}我们编写一个单元测试,通过 Mockito 隔离外部依赖:
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 testGetUserName_WhenUserExists() { // Arrange User mockUser = new User(1L, "Alice"); when(userRepository.findById(1L)).thenReturn(mockUser);
// Act String result = userService.getUserName(1L);
// Assert assertEquals("Alice", result); }
@Test void testGetUserName_WhenUserNotFound() { when(userRepository.findById(99L)).thenReturn(null);
String result = userService.getUserName(99L);
assertEquals("Unknown", result); }}良好实践:一个测试只验证一个断言。使用方法名清晰表达意图(例如 testGetUserName_WhenUserExists)。
2. 集成测试:验证交互
集成测试验证系统组件如何协同工作:数据库、文件系统、外部 API。它们比单元测试慢,但能更可靠地保证组件间协作的正确性。
2.1 Java 集成测试工具
- Spring Boot Test — 使用
@SpringBootTest启动应用程序上下文。 - Testcontainers — 在 Docker 容器中启动真实数据库(PostgreSQL、MySQL)。
- H2 — 用于快速测试的内存数据库。
2.2 使用 Spring Boot 和 Testcontainers 的集成测试示例
import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.t