如何在Rust中创建参数化测试?

Pea*_*nut 27 testing unit-testing rust

我想编写依赖于参数的测试用例.我应该为每个参数执行我的测试用例,我想看看每个参数是成功还是失败.

我习惯在Java中写这样的东西:

@RunWith(Parameterized.class)
public class FibonacciTest {
    @Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] {     
                 { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }  
           });
    }

    private int fInput;

    private int fExpected;

    public FibonacciTest(int input, int expected) {
        fInput= input;
        fExpected= expected;
    }

    @Test
    public void test() {
        assertEquals(fExpected, Fibonacci.compute(fInput));
    }
}
Run Code Online (Sandbox Code Playgroud)

我怎样才能实现与Rust相似的东西?简单的测试用例工作正常,但有些情况下还不够.

#[test]
fn it_works() {
    assert!(true);
}
Run Code Online (Sandbox Code Playgroud)

注意:我希望参数尽可能灵活,例如:从文件中读取它们,或者使用某个目录中的所有文件作为输入等.因此,硬编码的宏可能还不够.

Chr*_*gan 33

内置的测试框架不支持这个; 最常用的方法是使用宏为每个案例生成一个测试,如下所示:

macro_rules! fib_tests {
    ($($name:ident: $value:expr,)*) => {
    $(
        #[test]
        fn $name() {
            let (input, expected) = $value;
            assert_eq!(expected, fib(input));
        }
    )*
    }
}

fib_tests! {
    fib_0: (0, 0),
    fib_1: (1, 1),
    fib_2: (2, 1),
    fib_3: (3, 2),
    fib_4: (4, 3),
    fib_5: (5, 5),
    fib_6: (6, 8),
}
Run Code Online (Sandbox Code Playgroud)

这将产生与名称单独测试fib_0,fib_1,&C.

  • 这几乎就是我正在寻找的......我想根据目录中的文件列表生成我的案例。这可能不适用于宏,但我会看看我是否能想出一些办法。 (2认同)
  • 这可行,但缺点是 IDE 检查和自动完成功能(例如在 CLion 或 IntelliJ 中)不适用于宏内的代码。解决方法是将宏中的大部分代码移动到宏调用的方法中,然后让宏处理参数解构并传递给宏调用的方法的参数,并在宏调用的方法中进行断言。宏调用。它有点冗长,但作为一个无依赖的解决方案,它是完全可以接受的。 (2认同)
  • @RefaelSheinker:这是一个带有 Macro_rules 的简单结构宏,请查找。 (2认同)

Mic*_*ico 24

我的rstest箱子模仿pytest语法并提供了很大的灵活性。斐波那契示例可以非常简洁:

use rstest::rstest;

#[rstest]
#[case(0, 0)]
#[case(1, 1)]
#[case(2, 1)]
#[case(3, 2)]
#[case(4, 3)]
#[case(5, 5)]
#[case(6, 8)]
fn fibonacci_test(#[case] input: u32, #[case] expected: u32) {
    assert_eq!(expected, fibonacci(input))
}

pub fn fibonacci(input: u32) -> u32 {
    match input {
        0 => 0,
        1 => 1,
        n => fibonacci(n - 2) + fibonacci(n - 1)
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

/home/michele/.cargo/bin/cargo test
   Compiling fib_test v0.1.0 (file:///home/michele/learning/rust/fib_test)
    Finished dev [unoptimized + debuginfo] target(s) in 0.92s
     Running target/debug/deps/fib_test-56ca7b46190fda35

running 7 tests
test fibonacci_test::case_1 ... ok
test fibonacci_test::case_2 ... ok
test fibonacci_test::case_3 ... ok
test fibonacci_test::case_5 ... ok
test fibonacci_test::case_6 ... ok
test fibonacci_test::case_4 ... ok
test fibonacci_test::case_7 ... ok

test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Run Code Online (Sandbox Code Playgroud)

每个案例都作为一个单独的测试案例运行。

语法简单整洁,如果需要,您可以使用任何 Rust 表达式作为case参数中的值。

rstest还支持泛型和pytest类似装置。


不要忘记添加rstestdev-dependenciesin Cargo.toml

  • IntlliJ IDEA 宏扩展不是最好的...我想。在 VSCode 上工作就像一个魅力。如果我有时间(真的很难),我可以尝试查看 IntelliJ 代码,看看我是否可以做点什么。 (2认同)

Art*_*mGr 9

可能不是你问过相当的东西,但通过TestResult::discard快速检查可以测试使用随机生成的输入子集的功能.

extern crate quickcheck;

use quickcheck::{TestResult, quickcheck};

fn fib(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fib(n - 1) + fib(n - 2),
    }
}

fn main() {
    fn prop(n: u32) -> TestResult {
        if n > 6 {
            TestResult::discard()
        } else {
            let x = fib(n);
            let y = fib(n + 1);
            let z = fib(n + 2);
            let ow_is_ow = n != 0 || x == 0;
            let one_is_one = n != 1 || x == 1;
            TestResult::from_bool(x + y == z && ow_is_ow && one_is_one)
        }
    }
    quickcheck(prop as fn(u32) -> TestResult);
}
Run Code Online (Sandbox Code Playgroud)

我从这个Quickcheck教程中学习了Fibonacci测试.


PS当然,即使没有宏和快速检查,您仍然可以在测试中包含参数."把事情简单化".

#[test]
fn test_fib() {
    for &(x, y) in [(0, 0), (1, 1), (2, 1), (3, 2), (4, 3), (5, 5), (6, 8)].iter() {
        assert_eq!(fib(x), y);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,但这里的主要问题是许多场景(所有在 for 循环的“in”之后列出的场景)都与一个测试用例(“test_fib”)相关。因此,如果第一个方案失败,则不会执行任何其他列出的方案。 (5认同)

aur*_*lia 9

无需使用任何额外的包,您可以这样做,因为您可以编写返回 Result 类型的测试

#[cfg(test)]
mod tests {
    fn test_add_case(a: i32, b: i32, expected: i32) -> Result<(), String> {
        let result = a + b;
        if result != expected {
            Err(format!(
                "{} + {} result: {}, expected: {}",
                a, b, result, expected
            ))
        } else {
            Ok(())
        }
    }

    #[test]
    fn test_add() -> Result<(), String> {
        [(2, 2, 4), (1, 4, 5), (1, -1, 0), (4, 2, 0)]
            .iter()
            .try_for_each(|(a, b, expected)| test_add_case(*a, *b, *expected))?;

        Ok(())
    }
}
Run Code Online (Sandbox Code Playgroud)

您甚至会收到一条不错的错误消息:

    ---- tests::test_add stdout ----
Error: "4 + 2 result: 6, expected: 0"
thread 'tests::test_add' panicked at 'assertion failed: `(left == right)`
left: `1`,
right: `0`: the test returned a termination value with a non-zero status code (1) which indicates a failure', /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/test/src/lib.rs:194:5
Run Code Online (Sandbox Code Playgroud)

  • 根据 rust 文档, [`Iterator::try_for_each`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.try_for_each) 方法在“出现第一个错误时停止,返回该错误。” (2认同)

pah*_*olg 6

可以使用构建脚本基于任意复杂的参数和构建时已知的任何信息(包括您可以从文件加载的任何信息)构建测试

我们告诉 Cargo 构建脚本在哪里:

Cargo.toml

[package]
name = "test"
version = "0.1.0"
build = "build.rs"
Run Code Online (Sandbox Code Playgroud)

在构建脚本中,我们生成测试逻辑并使用环境变量将其放置在文件中OUT_DIR

构建.rs

fn main() {
    let out_dir = std::env::var("OUT_DIR").unwrap();
    let destination = std::path::Path::new(&out_dir).join("test.rs");
    let mut f = std::fs::File::create(&destination).unwrap();

    let params = &["abc", "fooboo"];
    for p in params {
        use std::io::Write;
        write!(
            f,
            "
#[test]
fn {name}() {{
    assert!(true);
}}",
            name = p
        ).unwrap();
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,我们在测试目录中创建一个文件,其中包含生成文件的代码。

测试/generated_test.rs

include!(concat!(env!("OUT_DIR"), "/test.rs"));
Run Code Online (Sandbox Code Playgroud)

就是这样。让我们验证测试是否运行:

[package]
name = "test"
version = "0.1.0"
build = "build.rs"
Run Code Online (Sandbox Code Playgroud)

  • 它输出到一个单独的目录(由“OUT_DIR”给出的目录),而不是源目录。 (2认同)

J. *_*Doe 6

使用https://github.com/frondeus/test-case箱。

例子:

#[test_case("some")]
#[test_case("other")]
fn works_correctly(arg: &str) {
    assert!(arg.len() > 0)
}
Run Code Online (Sandbox Code Playgroud)