如何在 doc 测试中断言 panic?

解读

国内 Rust 岗位面试时,面试官常把「文档测试(doc test)」作为考察候选人对测试体系理解深度的切入口。doc 测试本质是 rustdoc 把注释里的 ```rust 代码块提取出来,编译成独立二进制并执行。默认情况下,只要测试进程以非 0 退出码结束,就算失败;而 panic 会触发栈展开并返回 101,因此必须显式告诉 rustdoc“此处 panic 是预期行为”,否则测试会被判定为失败。能否正确写出「可 panic 的 doc 测试」直接体现候选人对 should_panic 属性、文档语法以及测试边界条件的掌握程度。

知识点

  1. doc 测试语法:在 /////! 注释里用 ```rust 代码块,可附加属性 should_panic
  2. 属性位置:必须写在代码块首行的 rust 之后,用逗号分隔,如 rust,should_panic。
  3. panic 触发方式
    • 直接 panic!()
    • 调用 unwrap 等会 panic 的 API
    • 使用 assert!/assert_eq! 失败
  4. should_panic 的匹配规则
    • 默认只要 panic 就算通过;
    • 可追加 expected = "substring" 做消息精确匹配,防止“误杀”其他 panic。
  5. 与单元测试的差异:单元测试用 #[test] + #[should_panic];doc 测试没有 #[test],靠代码块属性控制。
  6. 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 且(可选)消息匹配成功,测试才通过

拓展思考

  1. should_panic 的“假阳性”风险:如果代码重构后不再 panic,测试仍会通过,导致安全假设失效。国内金融、区块链项目对健壮性要求极高,建议同时维护一个“反向”单元测试,确保 panic 条件确实被触发。
  2. no_std 环境:嵌入式团队常在 no_std 下使用 panic_halt,此时 panic 行为可能直接被钩子截断,需确认链接脚本与 panic handler 不会吞掉退出码,否则 doc 测试永远通过。
  3. 覆盖率统计:国内不少公司用 tarpaulin 做覆盖率,should_panic 分支会被标记为已覆盖,但真实业务路径可能并未走到,需要结合模糊测试或 Miri 进一步验证。
  4. Rust 1.80+ 新特性:未来可能允许在 doc 测试里写 #[cfg(doc)] 条件编译,可用来隔离仅在文档阶段生效的辅助函数,减少对外暴露的 API 污染。