一、JUnit 详解
1. JUnit 核心概念
- 测试类:以
Test结尾的类(或通过@Test注解标记的方法)。 - 断言(Assertions):验证预期结果与实际结果是否一致(如
assertEquals())。 - 测试生命周期:通过注解管理测试的初始化和清理(如
@BeforeEach,@AfterEach)。 - 参数化测试:针对多组输入数据运行同一测试逻辑(JUnit 5+)。
2. JUnit 5 示例
(1) 基础测试
java 复制
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void testAdd() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3), "2+3 应等于 5");
}
@Test
void testDivideByZero() {
Calculator calculator = new Calculator();
assertThrows(ArithmeticException.class, () -> calculator.divide(10, 0));
}
}
(2) 生命周期管理
java 复制
import org.junit.jupiter.api.*;
class LifecycleTest {
@BeforeEach
void setup() {
System.out.println("每个测试方法执行前运行");
}
@AfterEach
void teardown() {
System.out.println("每个测试方法执行后运行");
}
@Test
void test1() {
System.out.println("运行测试1");
}
@Test
void test2() {
System.out.println("运行测试2");
}
}
(3) 参数化测试
java 复制
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
class ParameterizedTest {
@ParameterizedTest
@CsvSource({"2, 3, 5", "5, 7, 12", "-1, 1, 0"})
void testAdd(int a, int b, int expected) {
Calculator calculator = new Calculator();
assertEquals(expected, calculator.add(a, b));
}
}
二、Mockito 详解
1. Mockito 核心概念
- 模拟对象(Mock):通过
@Mock或mock()创建,隔离外部依赖。 - 注入依赖:使用
@InjectMocks自动注入模拟对象到被测类。 - 验证行为:通过
verify()检查方法是否按预期调用。 - 定义返回值:使用
when().thenReturn()模拟方法行为。
2. Mockito 示例
(1) 基础模拟
java 复制
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
// 被测类
class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(int id) {
return userRepository.findById(id);
}
}
// 模拟依赖
interface UserRepository {
User findById(int id);
}
class UserServiceTest {
@Test
void testGetUserById() {
// 1. 创建模拟对象
UserRepository mockUserRepository = mock(UserRepository.class);
// 2. 定义模拟行为
when(mockUserRepository.findById(1)).thenReturn(new User(1, "Alice"));
// 3. 注入模拟对象到被测类
UserService userService = new UserService(mockUserRepository);
// 4. 执行测试
User user = userService.getUserById(1);
// 5. 验证结果
assertEquals("Alice", user.getName());
verify(mockUserRepository).findById(1); // 确认方法被调用
}
}
(2) 验证调用次数
java 复制
@Test
void testSaveUser() {
UserRepository mockUserRepository = mock(UserRepository.class);
UserService userService = new UserService(mockUserRepository);
userService.saveUser(new User(2, "Bob"));
userService.saveUser(new User(3, "Charlie"));
// 验证 save 方法被调用了两次
verify(mockUserRepository, times(2)).save(any(User.class));
}
(3) 模拟异常场景
java 复制
@Test
void testUserNotFound() {
UserRepository mockUserRepository = mock(UserRepository.class);
UserService userService = new UserService(mockUserRepository);
when(mockUserRepository.findById(99)).thenThrow(new RuntimeException("User not found"));
assertThrows(RuntimeException.class, () -> userService.getUserById(99));
}
三、Mockito 高级用法
1. Spy 对象
- 部分模拟:真实对象的部分方法被监控,其余方法正常执行。
java 复制
@Test
void testSpy() {
List<String> list = new ArrayList<>();
List<String> spyList = spy(list);
doNothing().when(spyList).clear(); // 监控 clear() 方法
spyList.add("test");
verify(spyList).add("test"); // 验证 add() 被调用
spyList.clear(); // 实际调用真实方法
}
2. ArgumentCaptor 捕获参数
- 捕获方法参数:验证方法调用时传入的参数。
java 复制
@Test
void testCaptureArgument() {
UserRepository mockUserRepository = mock(UserRepository.class);
UserService userService = new UserService(mockUserRepository);
userService.saveUser(new User(4, "David"));
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
verify(mockUserRepository).save(userCaptor.capture());
User capturedUser = userCaptor.getValue();
assertEquals(4, capturedUser.getId());
}
四、JUnit 与 Mockito 结合实战
场景:测试订单服务(依赖数据库和外部 API)
java 复制
// 被测类
class OrderService {
private OrderRepository orderRepository;
private PaymentGateway paymentGateway;
public OrderService(OrderRepository orderRepository, PaymentGateway paymentGateway) {
this.orderRepository = orderRepository;
this.paymentGateway = paymentGateway;
}
public Order createOrder(OrderRequest request) {
// 1. 保存订单到数据库
Order order = orderRepository.save(request.toOrder());
// 2. 调用支付网关
paymentGateway.charge(order.getId(), order.getAmount());
return order;
}
}
// 测试类
class OrderServiceTest {
@Test
void testCreateOrder() {
// 1. 模拟依赖
OrderRepository mockRepo = mock(OrderRepository.class);
PaymentGateway mockGateway = mock(PaymentGateway.class);
// 2. 定义模拟行为
when(mockRepo.save(any(Order.class))).thenAnswer(invocation -> invocation.getArgument(0));
doNothing().when(mockGateway).charge(anyInt(), anyDouble());
// 3. 注入依赖并测试
OrderService orderService = new OrderService(mockRepo, mockGateway);
OrderRequest request = new OrderRequest(1001, 99.9);
Order order = orderService.createOrder(request);
// 4. 验证流程
verify(mockRepo).save(argThat(o -> o.getUserId() == 1001));
verify(mockGateway).charge(order.getId(), 99.9);
}
}
五、常见问题与解决
1. Mockito 无法模拟静态方法(JUnit 5)
- 原因:Mockito 默认不支持静态方法模拟。
- 解决:使用
mockito-inline库并启用静态模拟:java 复制
@ExtendWith(MockitoExtension.class) class MyTest { @Test void testStaticMethod() { try (MockedStatic<StaticClass> mocked = mockStatic(StaticClass.class)) { mocked.when(StaticClass.staticMethod()).thenReturn("mocked"); // 执行测试... } } }
2. 测试覆盖率低
- 工具:使用 JaCoCo 或 Cobertura 生成覆盖率报告。
- 优化:确保测试覆盖正常路径、边界条件和异常场景。
六、总结
- JUnit:核心是编写可重复的自动化测试,通过断言验证逻辑正确性。
- Mockito:通过模拟依赖隔离被测对象,支持复杂场景的单元测试。
- 最佳实践:
- 测试粒度小,聚焦单一功能。
- 使用
@BeforeEach初始化测试环境。 - 避免过度模拟,优先测试真实逻辑。
应用场景:
- JUnit:所有单元测试的基础框架。
- Mockito:依赖外部服务或复杂对象的场景(如数据库、API 调用)。



















