一、核心概念与差异对比
| 特性 | Mock | Stub |
|---|---|---|
| 核心目的 | 验证对象间的交互行为 | 提供预定义的固定响应 |
| 验证重点 | 方法调用次数、参数、顺序 | 不关注调用过程,只关注结果 |
| 行为模拟 | 可编程的智能模拟 | 静态的简单响应 |
| 适用场景 | 验证协作关系 | 隔离依赖、提供固定数据 |
| 复杂性 | 较高(需要设置预期行为) | 较低(简单硬编码响应) |
架构决策点:选择Mock还是Stub取决于测试目标 - 验证交互行为用Mock,替换依赖项用Stub
二、架构设计中的分层应用
测试金字塔中的定位
- 单元测试层:Mock主导(验证类间协作)
- 集成测试层:Stub主导(模拟外部依赖)
- 端到端测试:真实依赖(不使用模拟)
Spring测试架构
// 典型Spring测试分层
@SpringBootTest // 集成测试
class IntegrationTest {
@MockBean DependencyService service; // MockBean替代真实依赖
@Test void testIntegration() {
// 使用Stub提供预设数据
given(service.getData()).willReturn(new Data("stub value"));
}
}
@ExtendWith(MockitoExtension.class) // 单元测试
class UnitTest {
@Mock Dependency dependency;
@InjectMocks ServiceUnderTest service;
@Test void testBehavior() {
// 设置Mock行为并验证交互
when(dependency.process(any())).thenReturn(true);
service.execute();
verify(dependency, times(1)).process(any());
}
}
三、Spring生态中的实现详解
1. Stub实现方案
场景:为支付网关提供测试响应
// 定义支付网关接口
public interface PaymentGateway {
PaymentResult charge(Order order);
}
// 创建Stub实现
public class StubPaymentGateway implements PaymentGateway {
private final PaymentResult fixedResult;
public StubPaymentGateway(PaymentResult result) {
this.fixedResult = result;
}
@Override
public PaymentResult charge(Order order) {
// 静态响应,不包含业务逻辑
return fixedResult;
}
}
// 测试中使用
@Test
void testOrderProcessingWithStub() {
// 创建带成功响应的Stub
PaymentGateway stubGateway = new StubPaymentGateway(
new PaymentResult("SUCCESS", "TX-123"));
OrderService service = new OrderService(stubGateway);
Order order = new Order(100.0);
service.process(order);
assertThat(order.getStatus()).isEqualTo(OrderStatus.PAID);
}
2. Mock实现方案(Mockito框架)
场景:验证库存服务调用
@ExtendWith(MockitoExtension.class)
class InventoryServiceTest {
@Mock InventoryRepository mockRepo; // 创建Mock对象
@InjectMocks InventoryService inventoryService; // 注入被测试对象
@Test
void shouldUpdateInventoryWhenOrderPaid() {
// 准备测试数据
Order order = new Order("order-1");
order.addItem("prod-001", 2);
// 设置Mock行为
when(mockRepo.findByProductId("prod-001"))
.thenReturn(new Inventory("prod-001", 10));
// 执行测试
inventoryService.updateInventory(order);
// 验证交互行为
verify(mockRepo, times(1)).findByProductId("prod-001");
verify(mockRepo, times(1)).save(argThat(inv ->
inv.getQuantity() == 8)); // 验证保存参数
// 验证未发生的方法调用
verify(mockRepo, never()).delete(any());
}
}
四、高级模式与应用场景
1. 部分Mock(Spy对象)
场景:测试复杂服务中的特定方法
@Spy // 部分Mock:真实对象+可Mock特定方法
private EmailService emailService;
@Test
void testOrderNotification() {
// 模拟发送邮件方法
doNothing().when(emailService).sendConfirmation(any());
orderService.processOrder(order);
verify(emailService).sendConfirmation(order);
}
2. Spring Cloud Contract(契约测试)
架构方案:跨服务边界的Stub管理
提供方(定义契约):
// payment-contract.groovy
Contract.make {
request {
method POST()
url '/payments'
body([
orderId: anyUuid(),
amount: anyPositiveDouble()
])
}
response {
status 201
body([
status: "SUCCESS",
transactionId: regex('[0-9a-f]{8}-[0-9a-f]{4}')
])
}
}
消费方(测试中使用Stub):
@SpringBootTest
@AutoConfigureStubRunner(ids = "com.example:payment-service:+:stubs")
class OrderServiceContractTest {
@Test
void shouldProcessPayment() {
PaymentResult result = paymentClient.charge(new PaymentRequest(...));
assertThat(result.getStatus()).isEqualTo("SUCCESS");
}
}
五、架构师的最佳实践建议
-
分层使用策略
- 单元测试:80% Mock + 20% Stub
- 集成测试:20% Mock + 80% Stub
- 契约测试:100% Stub(基于提供方契约)
-
性能优化
// 重用Mock对象提升测试速度 @MockitoSettings(strictness = Strictness.LENIENT) public class ReusableMocksTest { @Mock static DatabaseConnection sharedConnection; } -
反模式警示
- ❌ 过度验证:
verify(mock, times(3)).trivialMethod() - ❌ Mock传递:
when(mockA.call()).thenReturn(mockB) - ❌ 脆弱的Stub:硬编码不合理的测试数据
- ❌ 过度验证:
-
现代替代方案
- 虚拟服务(Testcontainers):代替Stub提供更真实的模拟
@Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");- 代码生成Stub(OpenAPI Generator):基于API规范自动生成
六、架构决策树
graph TD
A[需要测试什么?]
A --> B{验证对象交互?}
B -->|是| C[使用Mock]
B -->|否| D{需要控制依赖行为?}
D -->|是| E[使用Stub]
D -->|否| F[使用真实对象]
C --> G{需要部分真实行为?}
G -->|是| H[使用Spy]
G -->|否| I[使用纯Mock]
E --> J{跨服务边界?}
J -->|是| K[使用契约测试Stub]
J -->|否| L[使用简单Stub实现]
作为系统架构师,我建议:在保证测试质量的前提下选择最简单的模拟方案。Mock适合验证内部协作,Stub适合隔离外部依赖,契约测试Stub则是微服务架构的必备工具。正确使用这些技术可以构建快速、可靠且维护成本低的测试体系。
1万+

被折叠的 条评论
为什么被折叠?



