用于生成多个单独测试的 Rust 宏
Rust macro to generate multiple individual tests
是否可以有一个生成独立测试的宏?我有两个文本文件,一个带有 输入 ,另一个带有输出。文本文件中的每一行都代表一个新测试。
目前,这就是我 运行 我的测试方式:
#[test]
fn it_works() {
let input = read_file("input.txt").expect("failed to read input");
let input = input.split("\n").collect::<Vec<_>>();
let output = read_file("output.txt").expect("failed to read output");
let output = output.split("\n").collect::<Vec<_>>();
input.iter().zip(output).for_each(|(a, b)| {
println!("a: {}, b: {}", a, b);
assert_eq!(b, get_result(a));
})
但是,如您所见,如果一个测试失败,则所有测试都会失败,因为在单个测试中存在循环。我需要每次迭代都是一个独立的测试,而不必自己重复。
所以我想知道是否可以通过使用宏来实现?
理想情况下,宏会输出如下内容:
#[test]
fn it_works_1() {
let input = read_file("input.txt").expect("failed to read input");
let input = input.split("\n").collect::<Vec<_>>();
let output = read_file("output.txt").expect("failed to read output");
let output = output.split("\n").collect::<Vec<_>>();
assert_eq!(output[0], get_result(input[0])); // first test
}
#[test]
fn it_works_2() {
let input = read_file("input.txt").expect("failed to read input");
let input = input.split("\n").collect::<Vec<_>>();
let output = read_file("output.txt").expect("failed to read output");
let output = output.split("\n").collect::<Vec<_>>();
assert_eq!(output[1], get_result(input[1])); // second test
}
// ... the N remaining tests: it_works_n()
您不能使用声明性宏来执行此操作,因为声明性宏无法生成标识符来命名测试函数。但是,您可以使用诸如 test-case
之类的 crate,它可以 运行 使用不同的输入进行相同的测试:
use test_case::test_case;
#[test_case(0)]
#[test_case(1)]
#[test_case(2)]
#[test]
fn it_works(index: usize) {
let input = read_file("input.txt").expect("failed to read input");
let input = input.split("\n").collect::<Vec<_>>();
let output = read_file("output.txt").expect("failed to read output");
let output = output.split("\n").collect::<Vec<_>>();
assert_eq!(output[index], get_result(input[index])); // first test
}
如果您有很多不同的输入要测试,您可以使用声明性宏来生成上面的代码,这将添加所有 #[test_case]
注释。
Peter Hall 回答后,我能够实现我想要的。我添加了 seq_macro
箱来生成重复的 #[test_case]
。也许有一种方法可以循环遍历所有测试用例,而不是手动定义测试数量(就像我所做的那样),但现在这很好:
macro_rules! test {
( $from:expr, $to:expr ) => {
#[cfg(test)]
mod tests {
use crate::{get_result, read_file};
use seq_macro::seq;
use test_case::test_case;
seq!(N in $from..$to {
#(#[test_case(N)])*
fn it_works(index: usize) {
let input = read_file("input.txt").expect("failed to read input");
let input = input.split("\n").collect::<Vec<_>>();
let output = read_file("output.txt").expect("failed to read output");
let output = output.split("\n").collect::<Vec<_>>();
let res = get_result(input[index]);
assert_eq!(
output[index], res,
"Test '{}': Want '{}' got '{}'",
input[index], output[index], res
);
}
});
}
};
}
test!(0, 82);
是否可以有一个生成独立测试的宏?我有两个文本文件,一个带有 输入 ,另一个带有输出。文本文件中的每一行都代表一个新测试。 目前,这就是我 运行 我的测试方式:
#[test]
fn it_works() {
let input = read_file("input.txt").expect("failed to read input");
let input = input.split("\n").collect::<Vec<_>>();
let output = read_file("output.txt").expect("failed to read output");
let output = output.split("\n").collect::<Vec<_>>();
input.iter().zip(output).for_each(|(a, b)| {
println!("a: {}, b: {}", a, b);
assert_eq!(b, get_result(a));
})
但是,如您所见,如果一个测试失败,则所有测试都会失败,因为在单个测试中存在循环。我需要每次迭代都是一个独立的测试,而不必自己重复。
所以我想知道是否可以通过使用宏来实现?
理想情况下,宏会输出如下内容:
#[test]
fn it_works_1() {
let input = read_file("input.txt").expect("failed to read input");
let input = input.split("\n").collect::<Vec<_>>();
let output = read_file("output.txt").expect("failed to read output");
let output = output.split("\n").collect::<Vec<_>>();
assert_eq!(output[0], get_result(input[0])); // first test
}
#[test]
fn it_works_2() {
let input = read_file("input.txt").expect("failed to read input");
let input = input.split("\n").collect::<Vec<_>>();
let output = read_file("output.txt").expect("failed to read output");
let output = output.split("\n").collect::<Vec<_>>();
assert_eq!(output[1], get_result(input[1])); // second test
}
// ... the N remaining tests: it_works_n()
您不能使用声明性宏来执行此操作,因为声明性宏无法生成标识符来命名测试函数。但是,您可以使用诸如 test-case
之类的 crate,它可以 运行 使用不同的输入进行相同的测试:
use test_case::test_case;
#[test_case(0)]
#[test_case(1)]
#[test_case(2)]
#[test]
fn it_works(index: usize) {
let input = read_file("input.txt").expect("failed to read input");
let input = input.split("\n").collect::<Vec<_>>();
let output = read_file("output.txt").expect("failed to read output");
let output = output.split("\n").collect::<Vec<_>>();
assert_eq!(output[index], get_result(input[index])); // first test
}
如果您有很多不同的输入要测试,您可以使用声明性宏来生成上面的代码,这将添加所有 #[test_case]
注释。
Peter Hall 回答后,我能够实现我想要的。我添加了 seq_macro
箱来生成重复的 #[test_case]
。也许有一种方法可以循环遍历所有测试用例,而不是手动定义测试数量(就像我所做的那样),但现在这很好:
macro_rules! test {
( $from:expr, $to:expr ) => {
#[cfg(test)]
mod tests {
use crate::{get_result, read_file};
use seq_macro::seq;
use test_case::test_case;
seq!(N in $from..$to {
#(#[test_case(N)])*
fn it_works(index: usize) {
let input = read_file("input.txt").expect("failed to read input");
let input = input.split("\n").collect::<Vec<_>>();
let output = read_file("output.txt").expect("failed to read output");
let output = output.split("\n").collect::<Vec<_>>();
let res = get_result(input[index]);
assert_eq!(
output[index], res,
"Test '{}': Want '{}' got '{}'",
input[index], output[index], res
);
}
});
}
};
}
test!(0, 82);