CsCheck 是一个 C# 测试库,用于编写基于属性、基于模型和可执行规格的测试。其核心思路是:让库去探索输入与动作序列,由库负责生成、收缩、打印和重放反例,而测试本身只需保持应用契约可见。建议从能够证伪断言的最小测试开始,而不是围绕 CsCheck 搭建一套测试框架。
选择测试形态
根据要检查的内容选择起点:
- 函数、往返、代数律或数据转换:从 Gen + Sample 开始,需要时阅读属性与生成器相关参考。
- 具有更简单参考表示的实现:从 SampleModelBased / SampleModelBasedAsync 开始,需要时阅读有状态与异步测试相关参考。
- 带显式状态、守卫和要求的协议或生命周期:从 Spec.From 开始,再用 Exhaustive 与 Conform,需要时阅读可执行规格相关参考。
- 异步资源,或期望必须纳入观测结果:使用生成的命令数组加 SampleAsync,并在每个用例内部做清理,需要时阅读有状态与异步测试相关参考。
- 失败属性、种子重放,或前后对比的缺陷演示:使用原生采样控制加一个具体回归用例,需要时阅读失败发现与重放相关参考。
只阅读任务所需的参考。示例使用 xUnit,但 CsCheck 本身与测试框架无关。应保留项目现有的框架与包管理约定。
先确立契约
识别一个可观测的要求,以及它应当拒绝的一个可能错误结果。把生成的输入、期望行为和实际观测分开。模型可以消费观测结果,但在把观测作为新状态接受之前,必须用先前的期望对其进行校验。不要用实现自身的答案去制造它自己的期望答案。
生成单个操作及其参数,让 CsCheck 去组合任意序列。有限域、加权边界值和前置条件是合理的建模选择。手写的完整场景是有用的回归用例,但不能替代对动作顺序的探索。
当拒绝本身是契约的一部分时,要让被拒绝的请求可测试。一个“仅在有余额时才能取款”的守卫无法测试余额不足时的拒绝。前置条件描述可以尝试什么;后置条件描述响应,包括允许的失败。
保持测试机制精简
优先使用组合的 Gen 值,而不是 Random、手选种子列表、自定义收缩器或“重试到通过”。
为每个生成的用例(包括收缩过程)构造全新的可变实现和模型。
使用普通 record、直接调用和有意义的比较。equal: 必须比较行为或状态;清理应放在 finally/using 中,而不是放在总是返回 true 的相等谓词里。
在普通发现运行中不要保留种子。重放失败时输出的种子,然后在回归必须经受生成器变化时固定具体输入。
简化时保留领域覆盖和历史反例。仅减少行数并不能证明替换后的测试检查了相同的行为。
验证正确的对象
Spec.Exhaustive 检查模型。Spec.Conform 通过适配器检查真实实现,且仅针对采样到的轨迹和比较过的观测。两个断言都应显式保留。检查不可达的要求、死锁和搜索截断;一次绿色的运行可能是空洞的。
在可行时,展示测试能拒绝一个已知有缺陷的实现,或在隔离副本中拒绝一个小的故意缺陷。应编译并执行示例,而不是假设 CsCheck 中存在同名的 FsCheck API。当原生基于模型的 API 的独立转换与比较适配时,应复用它们;自定义异步循环是有用的替代方案,但不是通用替代品。
API 基线
所链接的示例是在 .NET 8 上用 CsCheck 4.9.1 编译并运行的。其包标识的源码修订号为 5050355。在适配签名前请检查已安装的包。升级时,请核对 NuGet 包与上游源码;不要从其他库的 API 推断出主版本号或原生异步 Spec.Conform 支持。