C# 测试替身Test Double类型 C# Stub, Mock, Fake, Spy有什么区别

来源:这里教程网 时间:2026-02-21 17:40:31 作者:

Stub 是只返回预设值的“哑巴”替身

Stub 的核心作用是让被测代码能跑起来,不关心它是否被调用、怎么被调用,只负责在指定方法被调用时返回你硬编码的值。它没有行为验证能力。

典型场景:
HttpClient
调用外部 API 时,用
Stub
返回固定 JSON 字符串,避免网络依赖
常见错误:把
Stub
当成
Mock
用,试图验证调用次数或参数 —— 它根本不记录这些
实现方式:手写类继承接口并重写方法,或用
Moq
Setup
+
Returns
(但不配
Verify
示例:
mock.Setup(x => x.GetStatus()).Returns("OK");
—— 这本质是 Stub 行为

Mock 是带行为断言的“考官”替身

Mock
的关键特征是「可验证」:它会记录调用痕迹,并允许你在断言阶段检查是否被调用、调用几次、参数是否匹配。它既提供返回值,也承担验证职责。

典型场景:测试一个订单服务是否在库存不足时调用了
NotifyAdmin()
,且只调一次
容易踩坑:过度使用
Verify
导致测试脆弱 —— 比如验证了内部调用顺序,实际重构时逻辑没变但调用路径变了,测试就挂
参数差异:
Moq
Setup
定义响应,
Verify
执行断言;
NSubstitute
则用
Received(1)
风格
性能影响:所有调用都会被拦截和记录,大量 Mock 可能拖慢单元测试执行速度

Fake 是有真实逻辑但轻量的“简化版”实现

Fake
不是空壳,而是用内存集合、简单算法等替代真实依赖(比如数据库、文件系统),它自己能工作,只是绕过了外部副作用。

典型场景:用
InMemoryDatabase
替代
SqlServer
测试 EF Core 仓储层
常见误解:以为 Fake 就是 “随便写个假类”,其实它需要保持与真实实现一致的契约和基本正确性(比如
FakeRepository
Save
要真存进 List,
GetById
要真能查出来)
兼容性注意:Fake 往往不具备完整功能 —— 比如内存数据库不支持复杂 SQL 或事务隔离级别,测试覆盖不到边界情况 和 Stub 区别:Stub 是“静态应答”,Fake 是“可运行的最小闭环”

Spy 是带记录能力的“监听型”Stub

Spy
本质是 Stub 的增强版:它返回预设值,同时悄悄记下谁调了它、传了什么参数、调了多少次,供后续断言。它不主动抛异常或控制流程,只“看”不“判”。

典型场景:验证某个日志服务是否被传入了预期的错误消息,但不关心它内部怎么处理 Moq 中没有原生 Spy 类型,但可通过
Callback
实现:
mock.Setup(x => x.Log(It.IsAny<string>())).Callback<string>(msg => capturedMsg = msg);</string></string>
和 Mock 关键区别:Spy 不自带
Verify
方法,你需要自己保存状态并在
Assert
阶段手动比对;Mock 把记录+验证打包好了
容易忽略的点:Spy 的回调里不要做耗时或副作用操作(比如发 HTTP 请求),否则测试会不可靠 真正难的是选型时的权衡:Stub 够用就别上 Mock,Mock 过度会绑定实现细节,Fake 写不好反而掩盖集成缺陷,Spy 用得少但特定场景不可替代。别纠结名词,盯住你要验证的契约和要隔离的依赖。

相关推荐

热文推荐