mcpField
R
rust-testing-code-review

rust-testing-code-review

github.com/existential-birds测试与质量8589 次安装11 周前更新
代码仓库

关于 rust-testing-code-review

用于审查 Rust 测试代码的技能,涵盖测试组织、异步设置、断言、隔离与覆盖情况,并要求结合 Rust edition 检查及验证后再报告问题。

主要功能

  1. 01结合 Rust edition 的审查检查
  2. 02单元与集成测试组织
  3. 03异步测试和测试替身指导
  4. 04断言、隔离与错误路径检查
  5. 05并发及高级测试覆盖检查

适用场景

  1. 01审查 Rust crate 的测试布局
  2. 02检查异步测试和测试替身
  3. 03评估边界情况与错误路径覆盖
  4. 04审查并发测试实践

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/existential-birds/beagle --skill rust-testing-code-review

安装后在 Claude Code 中输入/rust-testing-code-review确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
advanced-testing.md
concurrency-testing.md
integration-tests.md
unit-tests.md

详细介绍

Rust 测试代码审查

审查工作流

  1. 检查 Rust 版本 — 查看 Cargo.toml 中的版本(2021 还是 2024)。版本 2024 会改变 if let 和尾表达式中的临时值作用域,并将 #[expect] 设为首选的 lint 抑制方式
  2. 检查测试组织方式 — 单元测试放在 #[cfg(test)] 模块中,集成测试放在 tests/ 目录中
  3. 检查异步测试设置 — 异步测试使用 #[tokio::test],并正确配置运行时。检查 mock 是否使用了 async-trait,而原生 trait async fn 是否也可行
  4. 检查断言 — 确保消息有意义、断言类型正确。检查 if let 断言是否受版本 2024 临时值作用域变化影响
  5. 检查测试隔离性 — 测试之间不得共享可变状态,并确保正确设置和清理。共享 fixture 优先使用 LazyLock,而不是 lazy_static!/once_cell
  6. 检查覆盖模式 — 确保测试覆盖错误路径和边界情况

门槛(硬性)

只有在满足每项通过条件(是/否,并提供具体凭据)后,才能进入 输出格式。

  1. 已记录版本 — 打开目标 crate 的 Cargo.toml(或工作区 [workspace.package] / 继承的版本),并记录 edition 值。在引用 Rust 2024 特有行为(if let / 尾部临时值的析构、#[expect] 与 #[allow] 的迁移、trait 中原生 async fn 作为默认方案)之前,通过条件:能够引用 edition = "…"(或注明“继承自工作区”)。如果版本不是 2024,不要将这些问题报告为版本 2024 的回归;如果仍有参考价值,最多列为信息性。
  2. dyn 与静态异步 mock — 建议使用 trait 中的原生 async fn 取代 async-trait 之前,检查 mock 是否用作 dyn Trait。通过条件:如果必须使用 dyn,则跳过该建议,或遵循有效模式(仍需使用 async-trait)。
  3. 验证协议 — 通过条件:列出任何问题之前,已完成 review-verification-protocol 技能中的步骤(参见提交前的发现)。

输出格式

按以下格式报告发现:

[FILE:LINE] ISSUE_TITLE
Severity: Critical | Major | Minor | Informational
Description of the issue and why it matters.

快速参考

问题类型参考
单元测试、断言、命名、快照、rstest、文档测试、#[expect]、LazyLock fixture、尾表达式作用域references/unit-tests.md
集成测试、异步测试、fixture、测试数据库、原生 async fn mock、if let 临时值作用域references/integration-tests.md
模糊测试、proptest、Miri、Loom 基础、mock 策略、stub/fake/mock/spy 分类、rstest 矩阵、paste!、build.rs 测试生成、criterion 基线 + black_box 使用规范、trybuild UI 测试、clippy lint 组references/advanced-testing.md
Loom 交错测试、Miri 未定义行为检查、shuttle、ThreadSanitizer、并发代码的 CI 矩阵references/concurrency-testing.md

审查清单

测试结构
  • 源文件中的 #[cfg(test)] mod tests 内包含单元测试
  • 集成测试放在 tests/ 目录中(每个模块或功能一个文件)
  • 测试模块中使用 use super::* 访问父模块项
  • 测试函数名称能描述场景:test_<function>_<scenario>_<expected>
  • 测试彼此独立,不依赖执行顺序
异步测试
  • 异步测试函数使用 #[tokio::test]
  • 测试多线程行为时使用 #[tokio::test(flavor = "multi_thread")]
  • 异步测试中不使用 block_on(直接使用 .await)
  • 为可能挂起的测试设置超时
  • mock trait 使用原生 async fn,而不是 async-trait crate(自 Rust 1.75 起稳定)
断言
  • 使用 assert_eq! / assert_ne! 进行值比较(错误信息比 assert! 更好)
  • 对不言自明的断言添加自定义消息
  • 使用 matches! 宏检查枚举变体
  • 使用 matches! 或模式匹配检查错误类型,而非比较字符串
  • 条件允许时,每个测试只写一个断言(更容易诊断失败)
  • 检查 if let 断言是否受 edition 2024 临时值作用域影响——条件中的临时值会更早释放,可能导致借用失效
  • 检查尾表达式返回值是否受 edition 2024 影响——尾表达式中的临时值会在局部变量之前释放
模拟与测试替身
  • 使用 trait 作为依赖注入的隔离接口(而非具体类型)
  • 将模拟实现保持精简——只实现测试所需的内容
  • 不模拟不归你所有的类型(将外部依赖封装在自有 trait 后面)
  • 使用辅助函数提供测试夹具,而非全局状态
  • 使用 std::sync::LazyLock 共享测试夹具,而非 lazy_static! 或 once_cell(自 Rust 1.80 起稳定)
错误路径测试
  • 测试 Result::Err 变体,而不只是正常路径
  • 检查具体的错误变体(而不只是检查“是否出错”)
  • 谨慎使用 #[should_panic]——优先使用返回 Result 的测试
测试中的 Lint 抑制
  • 对测试专用的抑制使用 #[expect(lint)],而非 #[allow(lint)](自 Rust 1.81 起稳定)
  • 在测试代码中的每个 #[expect] 或 #[allow] 上添加说明注释
  • 将过时的 #[allow] 属性迁移为 #[expect],以便自动清理
测试命名
  • 测试名称应像句子一样描述行为(而非 test_happy_path)
  • 使用嵌套的 mod 块组织相关测试
  • 测试名称遵循以下模式:<function>_should_<behavior>_when_<condition>
快照测试
  • 对复杂的结构化输出(JSON、YAML、HTML、CLI 输出)使用 cargo insta
  • 快照应小而聚焦(避免包含庞大对象)
  • 对不稳定字段(时间戳、UUID)使用脱敏
  • 将快照提交到 git 的 snapshots/ 目录
  • 简单值使用 assert_eq!,而非快照
参数化测试
  • 使用 rstest 避免为相似输入编写重复的测试函数
  • 使用 #[rstest] 和 #[case::name] 属性编写带有描述性参数的测试
  • 当多个测试需要相同的构造过程时,使用 #[fixture] 共享测试设置
  • 参数化测试仍应使用描述性的用例名称(而非仅使用 #[case(1)])
  • 与异步结合时,使用 #[rstest] #[tokio::test] 编写异步参数化测试
文档测试
  • 公共 API 函数应包含 /// # Examples 和可运行的代码
  • 文档测试兼具文档说明和正确性检查的作用
  • 使用以 # 开头的隐藏设置代码行,让示例保持简洁
  • cargo test --doc 应通过(nextest 不会运行文档测试)
并发测试

详细指南:references/concurrency-testing.md

  • 手写原子操作 / unsafe impl Send|Sync / 状态机应包含使用 loom::sync 和 loom::thread 替代实现(而非 std::sync)的 #[cfg(loom)] 测试
  • 使用 unsafe 操作原子变量、指针或 UnsafeCell 的 crate,应在 CI 中针对每个 PR 运行 cargo +nightly miri test --all-features(不能只在发布时运行,也不能使用模块级 cfg_attr(miri, ignore))
  • 无锁数据结构(AtomicPtr 栈/队列/链表)应包含 loom + Miri(Stacked Borrows 和 Tree Borrows)+ 由 proptest 驱动的操作序列测试
  • 不确定性输入(Instant::now、rand、环境变量、HashMap 迭代)不得出现在 loom::model 函数体中
  • Loom 任务应在 --release 模式下运行,并设置 LOOM_MAX_PREEMPTIONS 上限;loom 测试应放在单独的 tests/ 文件中,以免 --cfg loom 影响常规 cargo test
  • 不得将 nextest run -j1 作为覆盖竞态条件测试的证据;包含大量 FFI 的 unsafe extern "C" 路径应配置 ThreadSanitizer 任务
测试增强(Fake、Mock、Stub、Spy)

详细指南:references/advanced-testing.md

  • 测试替身按用途准确分类:stub(预置数据)、fake(可运行但经过简化的实现)、mock(预设期望并进行验证)、spy(记录调用,供事后检查)——术语使用准确
  • 在使用 fake 更简单的场景中,不使用 mockall 风格的 mock(对于仓储类 trait,内存数据库优于充满期望配置的 mock)
  • Fake 使用与生产实现相同的契约进行测试(trait 一致性测试);不允许出现偏差
  • 使用 Trait-as-seam 模式注入测试替身——生产代码依赖 trait,而非具体类型
性能测试(Criterion)

详细指南:references/advanced-testing.md

  • 基准测试使用 criterion::black_box(...) 防止常量折叠;指针类型的输入使用 black_box(input.as_ptr()),而非 black_box(&input)
  • 基准测试隔离 I/O——在 iter_batched 的 setup 闭包中执行准备工作(打开文件、分配内存),而非在测量闭包中执行
  • CI 使用 cargo bench -- --save-baseline main 保存基线;功能分支使用 --baseline main 进行比较;定义并执行回归阈值
  • Criterion 基准测试使用 --profile bench 构建(启用发布优化,并保留用于火焰图关联的调试符号)
  • 不使用 #[bench](不稳定、已弃用)——仅使用 criterion
  • 不引用单次计时结果作为证据;报告应展示 Criterion 统计引擎计算出的均值和方差
测试生成

详细指南:references/advanced-testing.md

  • 表驱动输入使用 rstest 和 #[case::name(...)](测试输出中显示描述性案例名称)
  • 多维输入矩阵使用 rstest,在多个参数上使用 #[values(...)](笛卡尔积),而非手动展开测试函数
  • 大型语料库使用 paste! 宏或 build.rs 代码生成,为每个文件/案例生成一个带有输入名称的测试
  • 生成的测试具有稳定的名称,可在 CI 日志差异中保持不变
Proc-Macro UI 测试(trybuild)

详细指南:references/advanced-testing.md

  • 会产生编译错误的 Proc-macro 使用 trybuild::TestCases 覆盖每条失败路径,并提供 .stderr 参考文件
  • 最新一次 rustc 升级后重新生成了 .stderr 输出(否则 CI 可能出现虚假失败);在 CONTRIBUTING 中记录 TRYBUILD=overwrite cargo test
  • CI 为 trybuild 任务固定特定的稳定版 rustc(.stderr 格式会随稳定版发布而变化)
  • 在 nightly 上跳过 trybuild 测试(nightly 诊断信息与稳定版不同)
Clippy Lint 组策略

详细指南:references/advanced-testing.md

  • clippy::correctness 设为 deny(始终如此——这些都是 bug)
  • 对库,将 clippy::suspicious 设为 warn 或 deny
  • 将 clippy::perf 设为 warn(可在热点路径上带来实际收益)
  • 对库启用 clippy::pedantic;抑制 lint 时使用带有说明的 #[expect(clippy::lint_name, reason = "...")],而非直接使用 #[allow]
  • CI 中不启用 clippy::nursery(实验性 lint;会随 rustc 版本变化)
  • 单独选择性启用 clippy::restriction lint;绝不整体启用该组

严重性校准

严重
  • 测试通过了,却没有实际验证行为(断言检查了错误的值)
  • 测试间共享可变状态,导致结果不稳定
  • 安全关键代码缺少错误路径测试
主要问题
  • 未提供 expected 消息的 #[should_panic](会捕获任何 panic,包括不符合预期的 panic)
  • 在测试设置中使用 unwrap(),掩盖了实际失败位置
  • 依赖执行顺序的测试
  • 在断言中使用带内联临时值的 if let,会在 edition 2024 的临时值作用域规则下出问题
  • 项目面向 edition 2024,且可使用 trait 中的原生 async fn 时,仍在 mock trait 上使用 async-trait
次要问题
  • 复杂比较缺少断言消息
  • 使用 assert!(x == y),而不是 assert_eq!(x, y)(错误消息较差)
  • 测试名称未能说明测试场景
  • 可提取为辅助函数的冗余设置代码
  • 使用 #[allow],而非能自动清理抑制的 #[expect]
  • 有 LazyLock 可用时,仍使用 lazy_static! 或 once_cell 作为测试 fixture
信息性建议
  • 建议通过 proptest 或 quickcheck 添加基于属性的测试
  • 建议为复杂输出添加快照测试
  • 提升覆盖率的机会

有效模式(不要标记)

  • 测试中的 unwrap() / expect() — 遇到意外错误时触发 panic 是正确的测试行为
  • 测试模块中的 use super::* — 这是访问父级条目的标准模式
  • 测试辅助函数上的 #[allow(dead_code)] — 并非每项测试都会用到这些辅助函数
  • 测试中的 clone() — 清晰度优先于性能
  • 较长的测试函数 — 集成测试可以很长;提取辅助函数不一定更清晰
  • 用于布尔值检查的 assert! — 表达式明显是布尔值时(如 .is_some()、.is_empty()),这样写没问题
  • 用于测试同一逻辑行为的多个断言 — 有时一个行为需要多项检查
  • 返回 Result 的测试函数中使用 unwrap() — 使用 ? 传播错误也可以,但不是必须的
  • 需要 dyn 分发的 mock trait 上使用 async-trait — trait 中的原生 async fn 不支持 dyn Trait;此时仍需要 async-trait
  • 测试辅助函数上带有理由说明的 #[expect] — 在测试代码中使用可自动清理的 lint 抑制是正确做法
  • 昂贵的共享测试 fixture 使用 LazyLock — 对测试全局变量进行线程安全的惰性初始化是合适的

提交发现前

报告任何问题前,请加载并遵循 review-verification-protocol 技能。

评论

支持 Markdown