4Growing Object-Oriented Software, Guided by Tests

模拟对象

Mock的艺术

Mock对象Stub行为验证状态验证

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第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)的核心功能有哪些?


4/7模拟对象
0%