一些问题(例如How can I create parameterized tests in Rust?)涉及使用宏在Rust中创建参数化单元测试。我需要将此技术用于generate a pair of unit tests for every pair of input files in a directory。单元测试自己只是调用一个简单的函数:
fn check_files(path1: &str, path2: &str, msg: &str) {
assert!(true, "FAILURE: {}: {} and {}.", msg, path1, path2);
}
我使用lazy_static
生成输入文件列表:
#![feature(plugin)]
#![plugin(interpolate_idents)]
extern crate glob;
#[macro_use]
extern crate lazy_static;
use glob::glob;
lazy_static! {
/// Glob all example files in the `tests/` directory.
static ref TEST_FILES: Vec<String> = glob("tests/*.java")
.expect("Failed to read glob pattern")
.into_iter()
.map(|res| res.unwrap().to_str().unwrap().to_string())
.collect::<Vec<String>>();
}
然后宏使用interpolate idents包来连接标识符以创建单元测试名称:
#[test]
fn test_glob_runner() {
// Define unit tests for a single pair of filenames.
macro_rules! define_tests {
($name1:tt, $name2:tt, $fname1:expr, $fname2:expr) => ( interpolate_idents! {
#[test]
fn [test_globbed_ $name1 _ $name2 _null]() {
check_files($fname1, $fname2, "null test");
}
#[test]
fn [test_globbed_ $name1 _ $name2 _non_null]() {
check_files($fname1, $fname2, "non-null test");
}
} )
}
// Write out unit tests for all pairs of given list of filenames.
macro_rules! test_globbed_files {
($d:expr) => {
for fname1 in $d.iter() {
for fname2 in $d.iter() {
// Remove directory and extension from `fname1`, `fname2`.
let name1 = &fname1[6..].split(".").next().unwrap();
let name2 = &fname1[6..].split(".").next().unwrap();
|| { define_tests!(name1, name2, fname1, fname2) };
}
}
}
}
// Test all pairs of files in the `tests/` directory.
test_globbed_files!(TEST_FILES);
}
这会产生以下编译器错误:
error: expected expression, found keyword `fn`
--> tests/test.rs:14:13
|
14 | fn [test_globbed_ $name1 _ $name2 _null]() {
| ^^
此错误消息对我来说没什么意义,尤其是因为define_tests
宏与code here类似。但是,我不确定在单元测试名称中使用name1
和name2
是否真的可行。
有一个complete but simplified example project on GitHub,只需克隆并运行cargo test
即可查看编译器错误。
答案 0 :(得分:2)
您在参数化测试中尝试接近的问题是TEST_FILES
仅在运行时计算,而您希望能够在编译时使用它来消除多个#[test]
函数。
为了使这项工作,你需要一些方法来在编译时计算TEST_FILES
。一种可能性是通过构建脚本在构建时迭代glob并将#[test]
函数写入可以包含在测试目录中的文件。
Cargo.toml
:[package]
# ...
build = "build.rs"
[build-dependencies]
glob = "0.2"
build.rs
:use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
extern crate glob;
use glob::glob;
fn main() {
let test_files = glob("tests/*.java")
.expect("Failed to read glob pattern")
.into_iter();
let outfile_path = Path::new(&env::var("OUT_DIR").unwrap()).join("gen_tests.rs");
let mut outfile = File::create(outfile_path).unwrap();
for file in test_files {
let java_file = file.unwrap().to_str().unwrap().to_string();
// FIXME: fill these in with your own logic for manipulating the filename.
let name = java_file;
let name1 = "NAME1";
let name2 = "NAME2";
write!(outfile, r#"
#[test]
fn test_globbed_{name}_null() {{
check_files({name1}, {name2}, "null test");
}}
#[test]
fn test_globbed_{name}_non_null() {{
check_files({name1}, {name2}, "non-null test");
}}
"#, name=name, name1=name1, name2=name2).unwrap();
}
}
tests/tests.rs
:include!(concat!(env!("OUT_DIR"), "/gen_tests.rs"));