A few questions (such as How can I create parameterized tests in Rust?) deal with using macros to create parameterised unit tests in Rust. I need to use this technique to generate a pair of unit tests for every pair of input files in a directory. The unit tests themselves just call a simple function:
几个问题(例如,我如何在Rust中创建参数化测试?)在Rust中使用宏来创建参数化的单元测试。我需要使用该技术为目录中的每一对输入文件生成一对单元测试。单元测试本身只调用一个简单的函数:
fn check_files(path1: &str, path2: &str, msg: &str) {
assert!(true, "FAILURE: {}: {} and {}.", msg, path1, path2);
}
I use lazy_static
to generate a list of input files:
我使用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>>();
}
And then the macros use the interpolate idents crate to concatenate identifiers to create the unit test names:
然后宏使用插补标识框来连接标识符以创建单元测试名称:
#[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);
}
This gives the following compiler error:
这会产生以下编译器错误:
error: expected expression, found keyword `fn`
--> tests/test.rs:14:13
|
14 | fn [test_globbed_ $name1 _ $name2 _null]() {
| ^^
This error message makes little sense to me, not least because the define_tests
macro is similar to the code here. However, I'm not sure that it's really possible to use name1
and name2
in the unit test name.
这个错误消息对我来说意义不大,尤其是因为DEFINE_TESTS宏类似于这里的代码。然而,我不确定是否真的可以在单元测试名称中使用name1和name2。
There is a complete but simplified example project on GitHub, just clone and run cargo test
to see the compiler error.
GitHub上有一个完整但简化的示例项目,只需克隆并运行Cargo测试即可查看编译器错误。
更多回答
Have you attempted to create a minimal reproducible example of your problem, emphasis on minimal? For example, I'm guessing you don't need lazy static or this interpolation crate, two tests, or any code inside the test. It seems to boil down to a confusion about when macros are expanded.
你有没有尝试为你的问题创建一个最小的可重现的例子,强调最小?例如,我猜您不需要惰性静态测试或这个插值箱、两个测试或测试中的任何代码。这似乎归结为对宏何时展开的困惑。
fname1.get(6..).unwrap()
-> &fname1[6..]
Fname1.get(6..).unwork()->&fname1[6..]
Generating a functions for specific filenames seems unnecessary, especially as they're just hard coding the filename. Could you instead run check_files
in a loop? Write a generator to spit out all possible filename combinations and loop over check_files
.
为特定的文件名生成函数似乎没有必要,尤其是当它们只是硬编码文件名的时候。您可以改为在循环中运行check_files吗?编写一个生成器来输出所有可能的文件名组合,并循环检查文件。
@Schwern that's a likely workaround, but it's certainly unsatisfying to those of us who love testing and having our test suites provide useful feedback about what has failed. Rust's test framework isn't quite there yet.
@Schwern这可能是一种解决办法,但对于我们这些热爱测试的人来说,这肯定是不满意的,因为我们的测试套件提供了关于失败的有用反馈。Rust的测试框架还没有完全到位。
@Shepmaster check_files
can provide that feedback, or a small wrapper around check_files
.
@SHEPMASTER CHECK_FILES可以提供反馈,或者对CHECK_FILES进行一个小包装。
The trouble with your attempted approach at parameterized tests is that TEST_FILES
is computed only at runtime, while you are expecting to be able to use it at compile time to stamp out the several #[test]
functions.
在参数化测试中尝试的方法的问题在于,TEST_FILES只在运行时计算,而您希望能够在编译时使用它来清除几个#[TEST]函数。
In order to make this work, you will need some way to compute TEST_FILES
at compile time. One possibility would be through a build script that iterates the glob at build time and writes out #[test]
functions to a file that can be included from your test directory.
为了实现这一点,您需要一些在编译时计算测试文件的方法。一种可能性是通过构建脚本,该脚本在构建时迭代GLOB,并将#[test]函数写出到可以从测试目录中包含的文件中。
In Cargo.toml
:
[package]
# ...
build = "build.rs"
[build-dependencies]
glob = "0.2"
In 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();
}
}
In tests/tests.rs
:
include!(concat!(env!("OUT_DIR"), "/gen_tests.rs"));
rstest can now do this. See docs.
Rstest现在可以做到这一点。请参见文档。
#[rstest]
fn for_each_file(#[files("src/**/*.rs")] #[exclude("test")] path: PathBuf)
{
assert!(check_file(&path))
}
更多回答
Well, this is a very different approach, and the compiler error isn't solved by replacing the lazy_static
with a hard-coded list of files...
这是一种非常不同的方法,编译器错误不是通过用硬编码的文件列表替换LAZY_STATIC来解决的……
@snim2 can you explain how it's a "very different approach" from the answer that also creates a build script that generates test code to a file and includes the generated file?
@snim2您能解释一下,这是一种与答案“非常不同的方法”吗?它还创建了一个构建脚本,用于向文件生成测试代码,并包括生成的文件。
In the sense that 1) this answer doesn't explain the error message, 2) it doesn't show how to use interpolate_ident
or similar to deal with the unit test names and 3) it involved the unit test runner running a file which does not exist on disk until the code is built. It's a good answer, but it leaves me a little unclear about where the approach in the original question went wrong.
从这个意义上说,1)这个答案没有解释错误消息,2)它没有说明如何使用interpolate_ident或类似的方法来处理单元测试名称,3)它涉及到单元测试运行器运行一个在构建代码之前不存在于磁盘上的文件。这是一个很好的答案,但它让我对原始问题中的方法哪里出了问题有点不清楚。
@snim2 the title of your question is "Generating unit tests for pairs of files on disk". Do you disagree that this answer answers the question you asked?
@snim2您的问题的标题是“为磁盘上的文件对生成单元测试”。你不同意这个答案回答了你提出的问题吗?
我是一名优秀的程序员,十分优秀!