1. Stub策略概述
适用场景
Stub 策略适用于模块边界接口测试,当被测模块依赖的外部组件尚未实现、不可用、难以控制或成本过高时,通过轻量模拟代替真实外部模块,隔离被测逻辑,快速验证交互正确性。典型场景包括:
- 底层硬件驱动、网络服务、数据库访问层开发阶段的上层功能验证
- 微服务间 RPC 接口尚未联调时的独立单元测试
- 第三方支付/认证 SDK 在测试环境无法真实调用时的替代方案
- 需要反复触发特定错误码或超时条件的异常路径覆盖
| 场景 | 用 Stub 的原因 | 典型副作用 |
|---|---|---|
| 硬件未就绪 | 避免真实硬件依赖 | 模拟时序差异 |
| 第三方接口不稳定 | 保证测试可重复 | 版本对齐问题 |
| 异常路径 | 可控注入错误 | 返回值不可覆盖全部真实行为 |
| 性能验证早期 | 排除异步等待 | 无法评估真实吞吐量 |
提示:Stub 不需要验证调用行为(如调用次数),只需返回预设数据。若需要验证交互行为,应选用 Mock。
Stub 与 Mock 的区别
两者均属于测试替身(Test Double),核心差异在于验证焦点:
| 维度 | Stub | Mock |
|---|---|---|
| 核心目的 | 提供预设输入/输出 | 验证交互行为(调用顺序、次数、参数) |
| 验证方式 | 通常不验证(仅返回值) | 显式断言(expect/verify) |
| 复杂程度 | 实现简单,固定响应 | 通常带行为记录和匹配器 |
| 适用测试类型 | 单元测试、集成测试的数据驱动 | 行为驱动测试、协议符合性验证 |
| 典型框架 | 手写函数/类、unittest.mock.Stub | Mockito、unittest.mock.Mock |
注意:切勿将 Stub 用于验证“方法被调用了多少次”——这是 Mock 的职责。滥用 Stub 做行为断言会导致测试脆弱且语义混乱。
模块边界定义
模块边界即接口契约的物理或逻辑分界线。在测试语境下,边界由以下要素界定:
- 协议/API 签名:输入参数类型、返回值、异常声明。
- 调用约束:同步/异步、超时、重试策略。
- 状态依赖:是否依赖外部状态(数据库、文件系统、缓存)。
- 容错策略:失败时的降级 / 熔断行为。
定义清晰的边界是设计有效 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 与真实接口签名不一致 —— 应使用接口契约严格校验(如编译期类型检查或 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 配置与测试数据解耦,便于多场景复用。