测试替身参考

概念关系

graph TD
    TD[”测试替身 (Test Double)”]
    TD --> Stub[”桩 (Stub)<br/>返回预设值”]
    TD --> Spy[”间谍 (Spy)<br/>记录调用信息”]
    TD --> Mock[”模拟对象 (Mock)<br/>预设行为 + 验证交互”]
    TD --> Dummy[”哑元 (Dummy)<br/>占位无逻辑”]
    TD --> Fake[”伪造 (Fake)<br/>轻量实现”]

三种主要替身对比

特性Stub(桩)Spy(间谍)Mock(模拟)
返回预设值✅ 是✅ 是✅ 是
记录调用次数❌ 否✅ 是✅ 是
验证调用参数❌ 否✅ 是✅ 是
验证调用顺序❌ 否✅ 是✅ 是
典型实现手动继承 + 重写手动记录调用Mockito 框架

手动实现示例

Stub

// 通过继承创建替身
class PriceCalculatorStub extends PriceCalculator {
    private int expectedPrice;
    
    public PriceCalculatorStub(int expectedPrice) {
        this.expectedPrice = expectedPrice;
    }
    
    @Override
    public int sum(int a, int b) {
        return expectedPrice;  // 返回预设值
    }
}

Spy

// 通过继承创建间谍
class ExpressServiceSpy extends ExpressService {
    private int callCount = 0;
    private List<Integer> calledParams = new ArrayList<>();
    
    @Override
    public int getTodayPrice() {
        callCount++;
        return 20;
    }
    
    public int getCallCount() { return callCount; }
    
    public boolean wasCalled() { return callCount > 0; }
}

Mock (Mockito)

// 使用 Mockito 框架
@Mock
private UserRepository userRepository;
 
@Test
void test() {
    // 预设行为 (Stub)
    when(userRepository.findByUsername("test"))
        .thenReturn(new User("test", "pass"));
    
    // 验证交互 (Mock)
    verify(userRepository).findByUsername("test");
    verify(userRepository, times(1)).findByUsername(any());
}

Mockito 常用 API

创建 Mock

// 注解方式
@Mock
private UserRepository userRepository;
 
// 编程方式
var mock = mock(UserRepository.class);

预设行为 (Stubbing)

// 基本返回
when(mock.findByUsername("test")).thenReturn(user);
 
// 异常
when(mock.findByUsername(any())).thenThrow(new RuntimeException());
 
// 多次调用不同返回
when(mock.findByUsername(any()))
    .thenReturn(user1)
    .thenReturn(user2);

验证交互 (Verification)

// 验证是否被调用
verify(mock).findByUsername("test");
 
// 验证调用次数
verify(mock, times(1)).findByUsername(any());
verify(mock, never()).deleteById(any());
verify(mock, atLeast(1)).findByUsername(any());
 
// 验证无多余交互
verifyNoMoreInteractions(mock);

选择指南

场景推荐方式
依赖简单,只需返回固定值手动 Stub
需要验证方法是否被调用Spy / Mockito verify
需要验证调用次数Mockito verify(times)
依赖复杂,多个返回值Mockito
测试遗留代码,无法使用框架手动 Stub / Spy