1. Stub策略概述

适用场景

Stub 策略适用于模块边界接口测试,当被测模块依赖的外部组件尚未实现、不可用、难以控制或成本过高时,通过轻量模拟代替真实外部模块,隔离被测逻辑,快速验证交互正确性。典型场景包括:

  • 底层硬件驱动、网络服务、数据库访问层开发阶段的上层功能验证
  • 微服务间 RPC 接口尚未联调时的独立单元测试
  • 第三方支付/认证 SDK 在测试环境无法真实调用时的替代方案
  • 需要反复触发特定错误码或超时条件的异常路径覆盖
场景用 Stub 的原因典型副作用
硬件未就绪避免真实硬件依赖模拟时序差异
第三方接口不稳定保证测试可重复版本对齐问题
异常路径可控注入错误返回值不可覆盖全部真实行为
性能验证早期排除异步等待无法评估真实吞吐量
提示:Stub 不需要验证调用行为(如调用次数),只需返回预设数据。若需要验证交互行为,应选用 Mock。

Stub 与 Mock 的区别

两者均属于测试替身(Test Double),核心差异在于验证焦点:

维度StubMock
核心目的提供预设输入/输出验证交互行为(调用顺序、次数、参数)
验证方式通常不验证(仅返回值)显式断言(expect/verify)
复杂程度实现简单,固定响应通常带行为记录和匹配器
适用测试类型单元测试、集成测试的数据驱动行为驱动测试、协议符合性验证
典型框架手写函数/类、unittest.mock.StubMockito、unittest.mock.Mock
注意:切勿将 Stub 用于验证“方法被调用了多少次”——这是 Mock 的职责。滥用 Stub 做行为断言会导致测试脆弱且语义混乱。

模块边界定义

模块边界即接口契约的物理或逻辑分界线。在测试语境下,边界由以下要素界定:

  1. 协议/API 签名:输入参数类型、返回值、异常声明。
  2. 调用约束:同步/异步、超时、重试策略。
  3. 状态依赖:是否依赖外部状态(数据库、文件系统、缓存)。
  4. 容错策略:失败时的降级 / 熔断行为。

定义清晰的边界是设计有效 Stub 的前提。下面是一个典型的模块边界接口定义示例(C++ 抽象):

// 模块边界接口:支付网关
class IPaymentGateway {
public:
    virtual ~IPaymentGateway() = default;
    virtual PaymentResult Charge(const Order& order, int timeout_ms) = 0;
    virtual void Refund(const std::string& transaction_id) = 0;
    virtual int GetBalance() const = 0;
};

基于此接口的 Stub 实现:

class StubPaymentGateway : public IPaymentGateway {
public:
    PaymentResult Charge(const Order& order, int) override {
        // 固定返回成功,金额直接回显
        return PaymentResult{true, order.amount, "txn-12345"};
    }
    void Refund(const std::string&) override {
        // 空操作,只记录日志
        std::cout << "[Stub] Refund called, no-op.\n";
    }
    int GetBalance() const override { return 999999; }
};
实现要点:Stub 仅需满足接口签名,不包含真实业务逻辑。返回值通常硬编码或通过构造函数注入预设数据。
Stub 在模块边界测试中的位置 被测模块 (SUT) 模块边界 Stub (模拟外部模块) 接口调用 预设响应 说明:Stub 通过依赖注入替换真实外部模块 在不修改被测模块代码的前提下隔离测试 可用? 选择 Stub 策略前需评估真实模块可用性

常见错误与优化方向

  • 错误:Stub 与真实接口签名不一致 —— 应使用接口契约严格校验(如编译期类型检查或 JSON Schema 验证)。
  • 错误:Stub 中混入断言逻辑 —— Stub 不负责验证调用行为,断言应回退到 Mock 层。
  • 优化:参数化 Stub 工厂 —— 通过配置文件或构造器注入预设数据,避免硬编码。
  • 优化:性能注意点 —— Stub 应避免 I/O 和复杂计算;如果需要超时模拟,使用异步回调而非 thread sleep。
// 参数化 Stub 示例(C++ 工厂模式)
struct StubConfig {
    bool charge_success;
    int simulate_delay_ms;
};

class ConfigurableStub : public IPaymentGateway {
public:
    explicit ConfigurableStub(StubConfig cfg) : config_(std::move(cfg)) {}
    PaymentResult Charge(const Order& order, int timeout_ms) override {
        std::this_thread::sleep_for(std::chrono::milliseconds(config_.simulate_delay_ms));
        if (!config_.charge_success) {
            return PaymentResult{false, 0, "stub-fail"};
        }
        return PaymentResult{true, order.amount, "txn-param"};
    }
private:
    StubConfig config_;
};
最佳实践:在 CI 中通过环境变量切换 Stub/真实模块实例,避免硬编码依赖。将 Stub 配置与测试数据解耦,便于多场景复用。