第4章:模拟对象(Mock Objects)
一、导读
1.1 本章学习目标
第4章深入讲解模拟对象(Mock Objects)——TDD 中最强大的工具之一。模拟对象让你能够隔离测试、验证行为、驱动设计。
1.2 为什么第四章如此重要
模拟对象不仅是测试工具,更是设计工具。通过模拟对象,你可以发现设计中的耦合问题,推动更好的抽象。
1.3 核心问题引导
Mock 和 Stub 有什么区别?
行为验证和状态验证如何选择?
过度使用 Mock 有什么问题?
如何通过 Mock 驱动设计?
二、核心概念详解
4.1 Mock vs Stub
Stub:提供预设的返回值
- 用于状态验证
- 不关心方法是否被调用
Mock:验证方法是否被正确调用
- 用于行为验证
- 关心方法调用的顺序和参数4.2 使用 Mockito
java
// Stub
when(userRepository.findById("123")).thenReturn(Optional.of(user));
// Mock
verify(emailService).send(eq("user@test.com"), anyString());
verifyNoMoreInteractions(emailService);
// 参数匹配
verify(repository).save(argThat(user ->
user.getName().equals("Alice") && user.getAge() == 30));4.3 通过 Mock 驱动设计
java
// 测试难以编写 → 设计有问题
@Test
public void shouldProcessOrder() {
// 需要创建太多真实对象
// 说明耦合太紧
}
// 引入依赖注入后
@Test
public void shouldProcessOrder() {
OrderService service = new OrderService(
mockPaymentGateway, mockInventoryService);
service.process(order);
verify(mockPaymentGateway).charge(order.total());
}
// 测试容易编写 → 设计更清晰三、实战案例
4.1 拍卖狙击手测试
java
@Test
public void shouldBidHigherWhenPriceChanged() {
Auction auction = mock(Auction.class);
AuctionSniper sniper = new AuctionSniper(auction, 200);
sniper.priceChanged(100);
verify(auction).bid(110);
}
@Test
public void shouldNotBidWhenAboveStopPrice() {
Auction auction = mock(Auction.class);
AuctionSniper sniper = new AuctionSniper(auction, 200);
sniper.priceChanged(250);
verify(auction, never()).bid(anyInt());
}四、本章小结
模拟对象是 TDD 的核心工具。Mock 验证行为,Stub 提供返回值。通过 Mock 可以驱动更好的设计,发现耦合问题。
五、思考与练习
Mock 和 Stub 如何选择?
过度使用 Mock 有什么风险?
如何通过 Mock 发现设计问题?
如何测试与数据库交互的代码?
Mock 框架(Mockito)的核心功能有哪些?