如何在Rust中按目录或正则表达式运行测试组?
核心思路
Rust的cargo test本身没有直接按文件路径正则匹配的选项,但可以通过测试目标配置、模块/测试名约定、Cargo别名这几种方式,实现类似Mocha的测试分组功能,满足cargo test-e2e这类快捷命令的需求。
方案1:利用Cargo测试目标(推荐)
通过在Cargo.toml中配置独立的测试目标,将不同分组的测试绑定到特定目录,无需手动维护测试文件列表。
步骤:
整理测试目录结构:
比如把端到端测试放在tests/e2e/目录下,功能组测试放在tests/feature_group1/:tests/ ├── e2e/ │ ├── checkout.test.rs │ └── login.test.rs └── feature_group1/ ├── user_management.test.rs └── payment.test.rs在Cargo.toml中配置测试目标:
为每个测试组添加[[test]]条目,用glob匹配目录下的所有测试文件:[[test]] name = "e2e" path = "tests/e2e/main.rs" harness = true # 自动包含e2e目录下所有.rs文件 glob = ["tests/e2e/**/*.rs"] [[test]] name = "feature-group1" path = "tests/feature_group1/main.rs" harness = true glob = ["tests/feature_group1/**/*.rs"]注意:每个测试目标需要一个入口文件(比如
main.rs),里面只需声明mod即可,Cargo会通过glob自动加载目录下的所有模块:// tests/e2e/main.rs mod checkout; mod login; // 新增测试文件时无需手动添加,glob会自动识别运行指定测试组:
直接通过测试目标名称运行:# 运行所有e2e测试 cargo test --test e2e # 运行feature-group1测试 cargo test --test feature-group1配置Cargo别名简化命令:
在Cargo.toml中添加别名,实现cargo test-e2e这类快捷命令:[alias] test-e2e = "test --test e2e" test-feature-group1 = "test --test feature-group1"之后只需执行:
cargo test-e2e cargo test-feature-group1
方案2:通过测试名/模块名约定过滤
如果不想修改Cargo配置,可以通过统一的测试名前缀或模块结构,结合cargo test的内置过滤功能实现分组。
方式A:测试名前缀
给同一组的测试统一加前缀,比如所有e2e测试命名为e2e_xxx:
#[test] fn e2e_checkout_flow() { // 测试逻辑 } #[test] fn e2e_login_success() { // 测试逻辑 }
运行时通过前缀过滤:
# 运行所有以e2e_开头的测试 cargo test e2e_
方式B:模块分组
把同一组测试放在同一个模块下:
// src/tests/e2e.rs pub mod e2e { #[test] fn checkout_flow() { /* ... */ } #[test] fn login_success() { /* ... */ } }
运行时通过模块路径过滤:
# 运行e2e模块下的所有测试 cargo test e2e::
方案3:自动生成测试入口(解决手动维护mod的问题)
如果觉得方案1中手动在main.rs添加mod麻烦,可以用build.rs自动生成测试入口文件,实现完全自动的目录匹配。
步骤:
创建build.rs:
在项目根目录创建build.rs,用glob读取指定目录下的所有测试文件,自动生成包含所有mod声明的入口文件:use glob::glob; use std::fs::write; use std::path::Path; fn main() { // 生成e2e测试的入口文件 generate_test_entry("tests/e2e", "tests/e2e/main.rs"); // 生成feature-group1的入口文件 generate_test_entry("tests/feature_group1", "tests/feature_group1/main.rs"); } fn generate_test_entry(dir: &str, output_path: &str) { let mut mod_lines = String::new(); // 匹配目录下所有.rs文件,排除main.rs本身 for entry in glob(&format!("{dir}/**/*.rs")).expect("Failed to read glob pattern") { let path = entry.unwrap(); let file_name = path.file_stem().unwrap().to_str().unwrap(); if file_name != "main" { mod_lines.push_str(&format!("mod {};\n", file_name)); } } // 写入入口文件 write(output_path, mod_lines).expect("Failed to write test entry file"); }添加依赖:
在Cargo.toml中添加globcrate到build-dependencies:[build-dependencies] glob = "0.3"运行测试:
之后新增测试文件时,build.rs会自动更新入口文件的mod声明,直接用方案1的命令运行即可。
对比各方案
| 方案 | 优点 | 缺点 |
|---|---|---|
| 测试目标+别名 | 官方支持,命令简洁,分组清晰 | 需要维护Cargo.toml配置 |
| 测试名/模块过滤 | 无需额外配置,灵活 | 依赖命名/模块约定,过滤规则不够直观 |
| build.rs自动生成入口 | 完全自动化,无需手动维护mod声明 | 需要编写build脚本,引入额外依赖 |
内容的提问来源于stack exchange,提问作者jtlindsey

