如何测试私有函数?
解读
在国内 Rust 岗位面试中,面试官提出“如何测试私有函数”并不是想听你背文档,而是考察三点:
- 是否真正理解 Rust 的模块可见性规则(模块边界与 pub 限定)
- 是否具备 “测试驱动”与“质量内建” 的实战习惯——国内大厂对单测覆盖率有硬性门禁(≥80%),私有函数逻辑往往更复杂,不测等于埋雷
- 能否在 “不破坏封装” 的前提下,给出可落地、可维护的工程方案
如果只说“把函数改成 pub”会被直接判负:既暴露实现细节,又违背设计初衷。正确姿势是在 编译单元内部 利用 Rust 的模块系统做“白盒”测试,同时兼顾代码组织与持续集成。
知识点
- 模块可见性:同一模块(文件)内所有项互相可见,无需 pub
- #[cfg(test)] 模块:仅在 cargo test 编译,不会污染生产二进制
- super 与 crate 路径:tests 子模块可通过 super:: 访问父模块私有项
- #[path] 属性:在 integration 目录下显式指定被测文件,突破目录可见性限制
- rustdoc --document-private-items:文档测试也可覆盖私有函数,但国内较少强制
- 代码覆盖率工具:tarpaulin / grcov,私有函数路径必须被单元测试命中才能统计到
答案
标准做法:“同文件内嵌 #[cfg(test)] 子模块” + “super:: 路径直接调用”。示例:
// src/network/packet.rs
fn checksum(buf: &[u8]) -> u32 {
buf.iter().fold(0u32, |acc, &b| acc.wrapping_add(b as u32))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn checksum_works() {
assert_eq!(checksum(b"123"), 6);
assert_eq!(checksum(b"\xff\xff"), 510);
}
}
关键点:
- tests 子模块与父模块同属一个编译单元,因此 super::checksum 可直接访问私有函数
- 整个 tests 模块被 #[cfg(test)] 包裹,cargo build 不会编译,发布二进制零冗余
- 运行 cargo test 时,lib 与 test 代码在同一次编译中完成,无需将函数暴露为 pub
如果私有函数位于 嵌套子模块 且测试代码量较大,可拆成独立文件,但仍保持同模块命名空间:
// src/parser/mod.rs
mod detail;
// src/parser/detail.rs
pub(super) fn parse_int(s: &str) -> Result<i32, ()> { ... }
// src/parser/detail/tests.rs
#[cfg(test)]
mod tests {
use super::super::detail::parse_int;
#[test] fn parse_zero() { assert_eq!(parse_int("0"), Ok(0)); }
}
并在 detail.rs 末尾 #[cfg(test)] #[path = "detail/tests.rs"] mod tests; 引入,既解耦文件体积,又不破坏可见性。
绝不推荐的做法:
- 把私有函数改成 pub 或 pub(crate) —— 暴露实现细节,后续重构困难
- 在 tests/ 集成测试目录里直接 use 私有函数 —— 集成测试是外部 crate,无法访问非 pub 项
- 使用 include! 宏把源文件硬塞进测试目录 —— 破坏依赖分析,增量编译失效
拓展思考
- 属性测试(proptest)与私有函数:对复杂私有算法编写 proptest 时,同样放在 #[cfg(test)] 子模块即可;记得在 CI 里加
cargo test --proptest-cases 10000保证覆盖率 - 条件编译与测试:若私有函数仅在 #[cfg(target_os = "linux")] 下存在,测试模块也要加相同 cfg,否则 Windows CI 会编译失败
- unsafe 私有函数:测试时务必搭配 Miri 运行
cargo +nightly miri test,在 CI 阶段捕获未定义行为 - 二进制 crate 的私有函数:src/main.rs 里无法被外部集成测试引用,可把主要逻辑抽到 src/lib.rs,主函数只做 CLI 转发,既方便单元测试,也符合国内“lib+bin”分层规范
- 覆盖率门禁:在 GitHub Actions 或 GitLab CI 中,用 grcov 生成 lcov 报告,私有函数行覆盖率低于 90% 直接拒绝 MR,这是国内一线 Rust 团队的硬门槛
掌握以上套路,面试时不仅能给出“标准答案”,还能结合工程化、CI、覆盖率侃侃而谈,让面试官确信你具备在大规模 Rust 代码库中落地质量保障的能力。