为磁盘上的文件对生成单元测试

问题描述 投票:1回答:1

一些问题(例如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。但是,我不确定在单元测试名称中使用name1name2是否真的可行。

有一个complete but simplified example project on GitHub,只需克隆并运行cargo test来查看编译器错误。

unit-testing macros rust glob
1个回答
2
投票

您在参数化测试中尝试接近的问题是TEST_FILES仅在运行时计算,而您希望能够在编译时使用它来消除几个#[test]函数。

为了使这项工作,您需要一些方法来在编译时计算TEST_FILES。一种可能性是通过构建脚本在构建时迭代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"));
© www.soinside.com 2019 - 2024. All rights reserved.