如何在 doc 测试中断言 panic?
解读
国内 Rust 岗位面试时,面试官常把「文档测试(doc test)」作为考察候选人对测试体系理解深度的切入口。doc 测试本质是 rustdoc
把注释里的 ```rust 代码块提取出来,编译成独立二进制并执行。默认情况下,只要测试进程以非 0 退出码结束,就算失败;而 panic 会触发栈展开并返回 101,因此必须显式告诉 rustdoc“此处 panic 是预期行为”,否则测试会被判定为失败。能否正确写出「可 panic 的 doc 测试」直接体现候选人对 should_panic
属性、文档语法以及测试边界条件的掌握程度。
知识点
- doc 测试语法:在
///
或//!
注释里用 ```rust 代码块,可附加属性should_panic
。 - 属性位置:必须写在代码块首行的
rust 之后,用逗号分隔,如
rust,should_panic。 - panic 触发方式:
- 直接
panic!()
- 调用
unwrap
等会 panic 的 API - 使用
assert!
/assert_eq!
失败
- 直接
- should_panic 的匹配规则:
- 默认只要 panic 就算通过;
- 可追加
expected = "substring"
做消息精确匹配,防止“误杀”其他 panic。
- 与单元测试的差异:单元测试用
#[test]
+#[should_panic]
;doc 测试没有#[test]
,靠代码块属性控制。 - CI 场景:国内公司普遍用 GitLab-CI 或 GitHub Actions,
cargo test --doc
失败会阻断合并,写错 should_panic 等于直接引入流水线红线。
答案
在文档注释里给代码块添加 should_panic
属性即可。示例:
/// 计算倒数,0 输入必须 panic
///
/// ```
/// # use your_crate::inverse;
/// # #[should_panic] // 错误:不能这么写
/// # inverse(0); // 错误:属性位置不对
/// ```
///
/// 正确写法:
/// ```rust,should_panic
/// # use your_crate::inverse;
/// inverse(0); // 预期 panic
/// ```
///
/// 若需匹配 panic 消息:
/// ```rust,should_panic,expected="divide by zero"
/// # use your_crate::inverse;
/// inverse(0);
/// ```
运行 cargo test --doc
时,rustdoc 会编译并执行该代码块,只有触发 panic 且(可选)消息匹配成功,测试才通过。
拓展思考
- should_panic 的“假阳性”风险:如果代码重构后不再 panic,测试仍会通过,导致安全假设失效。国内金融、区块链项目对健壮性要求极高,建议同时维护一个“反向”单元测试,确保 panic 条件确实被触发。
- no_std 环境:嵌入式团队常在
no_std
下使用panic_halt
,此时 panic 行为可能直接被钩子截断,需确认链接脚本与 panic handler 不会吞掉退出码,否则 doc 测试永远通过。 - 覆盖率统计:国内不少公司用 tarpaulin 做覆盖率,
should_panic
分支会被标记为已覆盖,但真实业务路径可能并未走到,需要结合模糊测试或 Miri 进一步验证。 - Rust 1.80+ 新特性:未来可能允许在 doc 测试里写
#[cfg(doc)]
条件编译,可用来隔离仅在文档阶段生效的辅助函数,减少对外暴露的 API 污染。