代码之家  ›  专栏  ›  技术社区  ›  snim2

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

  •  2
  • snim2  · 技术社区  · 7 年前

    一些问题(例如 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 查看编译器错误。

    1 回复  |  直到 7 年前
        1
  •  3
  •   Shepmaster Tim Diekmann    7 年前

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

    为了实现这一点,您需要某种方法来计算 TEST\u文件 在编译时。一种可能性是通过一个构建脚本,该脚本在构建时迭代glob并写出 #[测试] 函数添加到可以从测试目录中包含的文件。

    在里面 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"));