Mock与Stub


一、核心概念与差异对比

特性MockStub
核心目的验证对象间的交互行为提供预定义的固定响应
验证重点方法调用次数、参数、顺序不关注调用过程,只关注结果
行为模拟可编程的智能模拟静态的简单响应
适用场景验证协作关系隔离依赖、提供固定数据
复杂性较高(需要设置预期行为)较低(简单硬编码响应)

架构决策点:选择Mock还是Stub取决于测试目标 - 验证交互行为用Mock,替换依赖项用Stub


二、架构设计中的分层应用

测试金字塔中的定位
UI Tests
Integration Tests
Unit Tests
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管理

生成Stub
测试时下载
Provider
Stub Repository
Consumer

提供方(定义契约):

// 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");
    }
}

五、架构师的最佳实践建议

  1. 分层使用策略

    • 单元测试:80% Mock + 20% Stub
    • 集成测试:20% Mock + 80% Stub
    • 契约测试:100% Stub(基于提供方契约)
  2. 性能优化

    // 重用Mock对象提升测试速度
    @MockitoSettings(strictness = Strictness.LENIENT)
    public class ReusableMocksTest {
        @Mock static DatabaseConnection sharedConnection;
    }
    
  3. 反模式警示

    • ❌ 过度验证:verify(mock, times(3)).trivialMethod()
    • ❌ Mock传递:when(mockA.call()).thenReturn(mockB)
    • ❌ 脆弱的Stub:硬编码不合理的测试数据
  4. 现代替代方案

    • 虚拟服务(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则是微服务架构的必备工具。正确使用这些技术可以构建快速、可靠且维护成本低的测试体系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值