如何在Cargo二进制项目中正确组织单元测试?
如何在Cargo二进制项目中正确组织单元测试?
你遇到的这个问题其实是很多Rust新手都会踩的小坑——把测试代码错误地引入到了主二进制编译流程里,导致release构建时出现警告,还会把测试代码打包进最终的可执行文件里。别担心,Cargo本身就为我们设计了非常清晰的测试组织规范,下面给你讲几种最常用的正确做法:
一、最推荐:把单元测试直接写在被测试模块里(内联测试)
这种方式的核心是测试代码和被测试代码放在同一个文件里,但用#[cfg(test)]标记测试模块,告诉Cargo只有在运行cargo test的时候才编译这部分代码,release构建时会自动忽略。
具体修改步骤:
- 修改
src/rectangle.rs,在文件末尾添加测试模块:
#[derive(Debug)] pub struct Rectangle { pub width: u32, pub height: u32, } impl Rectangle { pub fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } } // 新增:用#[cfg(test)]包裹测试模块,仅在测试时编译 #[cfg(test)] mod tests { // 引入父模块的所有公共项 use super::*; #[test] fn larger_can_hold_smaller() { let larger = Rectangle { width: 8, height: 7, }; let smaller = Rectangle { width: 5, height: 1, }; assert!(larger.can_hold(&smaller)); } }
- 修改
src/main.rs,删除测试模块的引入:
把原来的代码改成:
mod rectangle; fn main() { println!("Hello, world! Doing some rectangle stuff now..."); }
这样做的好处:
- 测试代码和被测试逻辑紧挨着,后期维护时能快速找到对应测试
cargo build --release时完全不会编译测试代码,没有多余警告,二进制文件更干净- 运行
cargo test时,Cargo会自动执行这个模块里的测试,结果和你之前的一致
二、适合集成测试:独立的tests/目录
如果你的测试是跨模块的集成测试(比如测试多个模块配合的功能),或者想把测试代码和业务代码完全分开,Cargo支持在项目根目录创建tests/文件夹,里面的所有.rs文件都会被自动识别为集成测试,不需要在主代码里做任何引入。
具体操作:
- 在项目根目录创建
tests/文件夹,结构变成:
proj/ ├── src/ │ ├── main.rs │ └── rectangle.rs ├── tests/ │ └── rectangle_tests.rs └── Cargo.toml
- 在
tests/rectangle_tests.rs里写测试:
// 注意这里要使用你的项目名(Cargo.toml里的`name`字段)来引入模块 use proj::rectangle::*; #[test] fn larger_can_hold_smaller() { let larger = Rectangle { width: 8, height: 7, }; let smaller = Rectangle { width: 5, height: 1, }; assert!(larger.can_hold(&smaller)); }
- 保持
src/main.rs干净:不需要任何测试相关的引入,和上面的修改一样。
这样做的好处:
- 测试代码和业务代码完全分离,适合大型项目的集成测试
- Cargo会自动扫描
tests/下的所有测试文件,运行cargo test时会同时执行模块内的单元测试和tests/下的集成测试 - release构建完全不受测试代码影响
三、如果你坚持要把测试放在src/tests.rs里
如果你不想把测试写在rectangle.rs里,也不想用tests/目录,只想保留src/tests.rs,那可以通过条件编译让Cargo只在测试时引入这个模块:
修改src/main.rs的测试模块引入:
mod rectangle; // 只有在运行测试时才引入tests模块 #[cfg(test)] mod tests; fn main() { println!("Hello, world! Doing some rectangle stuff now..."); }
这样cargo build --release时会忽略mod tests;这一行,不会出现警告,也不会把测试代码编译进二进制。但这种方式不如前两种常用,因为测试和被测试代码分离较远,维护起来不太方便。
验证修改效果
现在你可以试试:
- 运行
cargo test:依然能正常执行所有测试,输出和之前一致 - 运行
cargo build --release:不会再出现任何关于mod tests;的警告,生成的二进制文件也不包含测试代码
这样就完美解决了你遇到的问题,同时符合Rust和Cargo的最佳实践~




