Rust Testing Code Review
Review Workflow
- 1. Check Rust edition — Note edition in
Cargo.toml (2021 vs 2024). Edition 2024 changes temporary scoping in if let and tail expressions, and makes #[expect] the preferred lint suppression - Check test organization — Unit tests in
#[cfg(test)] modules, integration tests in tests/ directory - Check async test setup —
#[tokio::test] for async tests, proper runtime configuration. Check for async-trait on mocks that could use native async fn in traits - Check assertions — Meaningful messages, correct assertion type. Review
if let assertions for edition 2024 temporary scope changes - Check test isolation — No shared mutable state between tests, proper setup/teardown. Prefer
LazyLock over lazy_static!/once_cell for shared fixtures - Check coverage patterns — Error paths tested, edge cases covered
Output Format
Report findings as:
CODEBLOCK0
Quick Reference
| Issue Type | Reference |
|---|
Unit tests, assertions, naming, snapshots, rstest, doc tests, #[expect], LazyLock fixtures, tail expression scope | references/unit-tests.md |
Integration tests, async testing, fixtures, test databases, native async fn mocks, if let temporary scope |
references/integration-tests.md |
| Fuzzing, property-based testing, Miri, Loom, benchmarking, compile
fail, custom harness, mocking strategies | references/advanced-testing.md |
Review Checklist
Test Structure
- - [ ] Unit tests in
#[cfg(test)] mod tests within source files - [ ] Integration tests in
tests/ directory (one file per module or feature) - [ ]
use super::* in test modules to access parent module items - [ ] Test function names describe the scenario: INLINECODE19
- [ ] Tests are independent — no reliance on execution order
Async Tests
- - [ ]
#[tokio::test] used for async test functions - [ ]
#[tokio::test(flavor = "multi_thread")] when testing multi-threaded behavior - [ ] No
block_on inside async tests (use .await directly) - [ ] Test timeouts set for tests that could hang
- [ ] Mock traits use native
async fn instead of async-trait crate (stable since Rust 1.75)
Assertions
- - [ ]
assert_eq! / assert_ne! used for value comparisons (better error messages than assert!) - [ ] Custom messages on assertions that aren't self-documenting
- [ ]
matches! macro used for enum variant checking - [ ] Error types checked with
matches! or pattern matching, not string comparison - [ ] One assertion per test where practical (easier to diagnose failures)
- [ ]
if let assertions reviewed for edition 2024 temporary scope — temporaries in conditions drop earlier, may invalidate borrows - [ ] Tail expression returns reviewed for edition 2024 — temporaries in tail expressions drop before local variables
Mocking and Test Doubles
- - [ ] Traits used as seams for dependency injection (not concrete types)
- [ ] Mock implementations kept minimal — only what the test needs
- [ ] No mocking of types you don't own (wrap external dependencies behind your own trait)
- [ ] Test fixtures as helper functions, not global state
- [ ]
std::sync::LazyLock used for shared test fixtures instead of lazy_static! or once_cell (stable since Rust 1.80)
Error Path Testing
- - [ ]
Result::Err variants tested, not just happy paths - [ ] Specific error variants checked (not just "is error")
- [ ]
#[should_panic] used sparingly — prefer Result-returning tests
Lint Suppression in Tests
- - [ ]
#[expect(lint)] used instead of #[allow(lint)] for test-specific suppressions (stable since Rust 1.81) - [ ] Justification comment on every
#[expect] or #[allow] in test code - [ ] Stale
#[allow] attributes migrated to #[expect] for self-cleaning behavior
Test Naming
- - [ ] Test names read like sentences describing behavior (not
test_happy_path) - [ ] Related tests grouped in nested
mod blocks for organization - [ ] Test names follow pattern: INLINECODE46
Snapshot Testing
- - [ ]
cargo insta used for complex structural output (JSON, YAML, HTML, CLI output) - [ ] Snapshots are small and focused (not huge objects)
- [ ] Redactions used for unstable fields (timestamps, UUIDs)
- [ ] Snapshots committed to git in
snapshots/ directory - [ ] Simple values use
assert_eq!, not snapshots
Parametrized Testing
- - [ ]
rstest used to avoid duplicated test functions for similar inputs - [ ]
#[rstest] with #[case::name] attributes for descriptive parametrized tests - [ ]
#[fixture] used for shared test setup when multiple tests need same construction - [ ] Parametrized tests still have descriptive case names (not just
#[case(1)]) - [ ] Combined with async:
#[rstest] #[tokio::test] for async parametrized tests
Doc Tests
- - [ ] Public API functions have
/// # Examples with runnable code - [ ] Doc tests serve as both documentation and correctness checks
- [ ] Hidden setup lines prefixed with
# to keep examples clean - [ ]
cargo test --doc passes (nextest doesn't run doc tests)
Severity Calibration
Critical
- - Tests that pass but don't actually verify behavior (assertions on wrong values)
- Shared mutable state between tests causing flaky results
- Missing error path tests for security-critical code
Major
- -
#[should_panic] without expected message (catches any panic, including wrong ones) - INLINECODE61 in test setup that hides the real failure location
- Tests that depend on execution order
- INLINECODE62 with inline temporary in assertion that breaks under edition 2024 temporary scoping
- INLINECODE63 on mock traits when native
async fn in traits is available and project targets edition 2024
Minor
- - Missing assertion messages on complex comparisons
- INLINECODE65 instead of
assert_eq!(x, y) (worse error messages) - Test names that don't describe the scenario
- Redundant setup code that could be extracted to a helper
- INLINECODE67 used where
#[expect] would provide self-cleaning suppression - INLINECODE69 or
once_cell used for test fixtures when LazyLock is available
Informational
- - Suggestions to add property-based tests via
proptest or INLINECODE73 - Suggestions to add snapshot testing for complex output
- Coverage improvement opportunities
Valid Patterns (Do NOT Flag)
- -
unwrap() / expect() in tests — Panicking on unexpected errors is the correct test behavior use super::* in test modules — Standard pattern for accessing parent items#[allow(dead_code)] on test helpers — Helper functions may not be used in every testclone() in tests — Clarity over performance- Large test functions — Integration tests can be long; extracting helpers isn't always clearer
assert! for boolean checks — Fine when the expression is clearly boolean (.is_some(), .is_empty())- Multiple assertions testing one logical behavior — Sometimes one behavior needs multiple checks
unwrap() on Result-returning test functions — Propagating with ? is also fine but not requiredasync-trait on mock traits requiring dyn dispatch — Native async fn in traits doesn't support dyn Trait; async-trait is still needed there#[expect] with justification on test helpers — Self-cleaning lint suppression is correct in test codeLazyLock for expensive shared test fixtures — Thread-safe lazy init is appropriate for test globals
Before Submitting Findings
Load and follow beagle-rust:review-verification-protocol before reporting any issue.
Rust 测试代码审查
审查工作流
- 1. 检查 Rust 版本 — 注意 Cargo.toml 中的版本(2021 版 vs 2024 版)。2024 版改变了 if let 和尾表达式中的临时作用域,并使 #[expect] 成为首选的 lint 抑制方式
- 检查测试组织 — 单元测试在 #[cfg(test)] 模块中,集成测试在 tests/ 目录下
- 检查异步测试设置 — 异步测试使用 #[tokio::test],正确的运行时配置。检查 mock 中是否使用了 async-trait,而实际上可以使用 trait 中的原生 async fn
- 检查断言 — 有意义的消息,正确的断言类型。检查 if let 断言在 2024 版临时作用域变化下的影响
- 检查测试隔离性 — 测试之间没有共享的可变状态,正确的设置/清理。对于共享 fixture,优先使用 LazyLock 而非 lazystatic!/oncecell
- 检查覆盖模式 — 测试了错误路径,覆盖了边界情况
输出格式
按以下格式报告发现:
text
[文件:行号] 问题标题
严重程度:严重 | 主要 | 次要 | 信息性
问题描述及其重要性说明。
快速参考
| 问题类型 | 参考 |
|---|
| 单元测试、断言、命名、快照、rstest、文档测试、#[expect]、LazyLock fixture、尾表达式作用域 | references/unit-tests.md |
| 集成测试、异步测试、fixture、测试数据库、原生 async fn mock、if let 临时作用域 |
references/integration-tests.md |
| 模糊测试、基于属性的测试、Miri、Loom、基准测试、compile
fail、自定义测试框架、mock 策略 | references/advanced-testing.md |
审查清单
测试结构
- - [ ] 单元测试在源文件中的 #[cfg(test)] mod tests 内
- [ ] 集成测试在 tests/ 目录下(每个模块或功能一个文件)
- [ ] 测试模块中使用 use super::* 访问父模块项
- [ ] 测试函数名描述场景:test<函数><场景>_<预期>
- [ ] 测试相互独立——不依赖执行顺序
异步测试
- - [ ] 异步测试函数使用 #[tokio::test]
- [ ] 测试多线程行为时使用 #[tokio::test(flavor = multithread)]
- [ ] 异步测试中不使用 block
on(直接使用 .await)
[ ] 为可能挂起的测试设置超时[ ] Mock trait 使用原生 async fn 而非 async-trait crate(自 Rust 1.75 起稳定)
断言
- - [ ] 值比较使用 asserteq! / assertne!(比 assert! 提供更好的错误信息)
- [ ] 对非自文档化的断言添加自定义消息
- [ ] 枚举变体检查使用 matches! 宏
- [ ] 错误类型使用 matches! 或模式匹配检查,而非字符串比较
- [ ] 实际可行时每个测试一个断言(更容易诊断失败)
- [ ] 检查 if let 断言在 2024 版临时作用域下的影响——条件中的临时变量提前释放,可能使借用失效
- [ ] 检查尾表达式返回在 2024 版下的影响——尾表达式中的临时变量在局部变量之前释放
Mock 和测试替身
- - [ ] 使用 trait 作为依赖注入的接缝(而非具体类型)
- [ ] Mock 实现保持最小化——只包含测试所需内容
- [ ] 不 mock 你不拥有的类型(将外部依赖包装在自己的 trait 后面)
- [ ] 测试 fixture 作为辅助函数,而非全局状态
- [ ] 共享测试 fixture 使用 std::sync::LazyLock 而非 lazystatic! 或 oncecell(自 Rust 1.80 起稳定)
错误路径测试
- - [ ] 测试 Result::Err 变体,而不仅仅是正常路径
- [ ] 检查具体的错误变体(不仅仅是是否为错误)
- [ ] 谨慎使用 #[should_panic]——优先使用返回 Result 的测试
测试中的 Lint 抑制
- - [ ] 测试特定的抑制使用 #[expect(lint)] 而非 #[allow(lint)](自 Rust 1.81 起稳定)
- [ ] 测试代码中的每个 #[expect] 或 #[allow] 都附有理由注释
- [ ] 将过时的 #[allow] 属性迁移到 #[expect] 以实现自我清理行为
测试命名
- - [ ] 测试名读起来像描述行为的句子(而非 testhappypath)
- [ ] 相关测试分组在嵌套的 mod 块中以实现组织
- [ ] 测试名遵循模式:<函数>should<行为>when<条件>
快照测试
- - [ ] 复杂结构化输出(JSON、YAML、HTML、CLI 输出)使用 cargo insta
- [ ] 快照小而精(不是巨大的对象)
- [ ] 对不稳定字段(时间戳、UUID)使用脱敏处理
- [ ] 快照提交到 git 的 snapshots/ 目录中
- [ ] 简单值使用 assert_eq!,而非快照
参数化测试
- - [ ] 使用 rstest 避免为相似输入重复测试函数
- [ ] 使用 #[rstest] 配合 #[case::name] 属性进行描述性参数化测试
- [ ] 当多个测试需要相同构造时,使用 #[fixture] 进行共享测试设置
- [ ] 参数化测试仍有描述性的 case 名称(不仅仅是 #[case(1)])
- [ ] 与异步结合:异步参数化测试使用 #[rstest] #[tokio::test]
文档测试
- - [ ] 公共 API 函数有 /// # Examples 并包含可运行代码
- [ ] 文档测试既作为文档也作为正确性检查
- [ ] 隐藏的设置行以 # 为前缀,保持示例整洁
- [ ] cargo test --doc 通过(nextest 不运行文档测试)
严重程度校准
严重
- - 通过但实际未验证行为的测试(对错误值进行断言)
- 测试之间共享可变状态导致不稳定结果
- 安全关键代码缺少错误路径测试
主要
- - #[should_panic] 没有 expected 消息(捕获任何 panic,包括错误的)
- 测试设置中的 unwrap() 隐藏了真正的失败位置
- 依赖执行顺序的测试
- 断言中带有内联临时变量的 if let 在 2024 版临时作用域下失效
- 当 trait 中原生 async fn 可用且项目目标为 2024 版时,mock trait 仍使用 async-trait
次要
- - 复杂比较缺少断言消息
- 使用 assert!(x == y) 而非 asserteq!(x, y)(错误信息更差)
- 测试名未描述场景
- 可提取到辅助函数的冗余设置代码
- 使用 #[allow] 而 #[expect] 可提供自我清理抑制
- 当 LazyLock 可用时,测试 fixture 仍使用 lazystatic! 或 once_cell
信息性
- - 建议通过 proptest 或 quickcheck 添加基于属性的测试
- 建议为复杂输出添加快照测试
- 覆盖率改进机会
有效模式(不要标记)
- - 测试中的 unwrap() / expect() — 对意外错误 panic 是正确的测试行为
- 测试模块中的 use super:: — 访问父项的标准模式
- 测试辅助函数上的 #[allow(deadcode)] — 辅助函数可能并非每个测试都使用
- 测试中的 clone() — 清晰性优先于性能
- 大型测试函数 — 集成测试可能很长;提取辅助函数并不总是更清晰
- 布尔检查使用 assert! — 当表达式明显为布尔值时没问题(.issome()、.is_empty())
- 测试一个逻辑行为的多个断言 — 有时一个行为需要多次检查
- *返回