如何将Rust单元测试移至独立文件,保持合理模块结构且同级存放?
概述
我编写Rust单元测试时,常规做法是把测试代码和被测代码放在同一个文件里。但当测试代码行数远多于被测代码时,文件会变得臃肿——我实际开发中经常遇到这种情况,曾为千行左右的业务代码写过数千行测试,很多时候是因为测试需要大量状态初始化,而且这些逻辑没法封装成单一辅助函数。
问题总结
怎么把测试代码移到独立文件里,同时又不搞出怪异的模块结构?我希望测试文件和被测文件放在同一目录下,但好像Rust里没有直接的方法,是不是我漏了什么方案?
当前实现示例
我现在的目录结构和代码是这样的:
目录结构
lib.rs ./my_module/ mod.rs implementation.rs ./implementation/ the_test.rs
代码实现
lib.rs:
pub mod my_module;
my_module/mod.rs:
pub mod implementation;
my_module/implementation.rs:
// 示例函数 fn str_to_bool(input: &str) -> bool { match input { "true" => true, _ => false, } } mod the_test;
my_module/implementation/the_test.rs:
#[cfg(test)] #[test] fn str_to_bool_test() { let input = "true"; assert!(str_to_bool(input) == true); }
当前实现的弊端
虽然把测试代码分离到了独立文件,但测试文件被放在了被测文件的子目录里,结构很别扭。我理想的状态是测试文件和被测文件同级存放,但又希望测试模块是被测模块的子模块,不想把模块声明移到上层。
可行解决方案
方案1:利用-test.rs命名约定(推荐用于需访问私有成员的单元测试)
Rust支持将测试文件命名为[模块名]-test.rs,并在被测模块中通过条件编译引入,这样测试文件就能和被测文件同级存放,同时保持测试模块作为被测模块的子模块:
调整后目录结构
lib.rs ./my_module/ mod.rs implementation.rs implementation-test.rs
修改被测模块文件
在my_module/implementation.rs末尾添加:
#[cfg(test)] mod implementation_test;
Rust会自动将implementation-test.rs解析为implementation_test模块,无需额外配置。
测试文件内容(implementation-test.rs)
#[cfg(test)] use super::*; // 引入被测模块的所有内容,包括私有成员 #[test] fn str_to_bool_test() { let input = "true"; assert!(str_to_bool(input)); }
这种方式既保持了测试模块的从属关系,又让文件结构整洁,还能访问被测模块的私有成员,完全符合需求。
方案2:使用根目录tests目录(集成测试)
如果你的测试不需要访问被测模块的私有成员,可以使用Rust官方推荐的集成测试目录tests,测试文件直接放在项目根目录下的tests文件夹中:
目录结构
lib.rs tests/ implementation_test.rs ./my_module/ mod.rs implementation.rs
测试文件内容(tests/implementation_test.rs)
use your_crate_name::my_module::implementation; #[test] fn str_to_bool_test() { let input = "true"; assert!(implementation::str_to_bool(input)); }
注意这里需要将your_crate_name替换为你的 crate 名称,且只能访问被测模块的公共成员。
内容的提问来源于stack exchange,提问作者user2138149

