我想编写依赖于参数的测试用例。我的测试用例应该针对每个参数执行,我想看看每个参数是成功还是失败。
我过去常常在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));
}
}
如何实现与Rust类似的功能?简单的测试用例工作正常,但有些情况下还不够。
#[test]
fn it_works() {
assert!(true);
}
注意:我希望参数尽可能灵活,例如:从文件中读取它们,或者使用某个目录中的所有文件作为输入等。因此硬编码的宏可能还不够。
答案 0 :(得分:22)
内置测试框架不支持此功能;最常用的方法是使用宏为每个案例生成一个测试,如下所示:
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),
}
这会产生名称为fib_0
,fib_1
,&c;
答案 1 :(得分:7)
可能不完全是你要求的,但是通过TestResult::discard
使用quickcheck,您可以使用随机生成的输入的子集来测试函数。
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);
}
我从this Quickcheck tutorial进行了斐波纳契测试。
P.S。当然,即使没有宏和快速检查,您仍然可以在测试中包含参数。 “保持简单”。
#[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);
}
}
答案 2 :(得分:4)
可以使用a build script基于任意复杂的参数和构建时已知的任何信息(包括可以从文件加载的任何信息)构建测试。
我们告诉Cargo构建脚本在哪里:
<强> Cargo.toml 强>
[package]
name = "test"
version = "0.1.0"
build = "build.rs"
在构建脚本中,我们生成测试逻辑并使用环境变量OUT_DIR
将其放在一个文件中:
<强> build.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();
}
}
最后,我们在tests目录中创建一个包含生成文件代码的文件。
<强>测试/ generated_test.rs 强>
include!(concat!(env!("OUT_DIR"), "/test.rs"));
那就是它。让我们验证测试是否已运行:
$ cargo test
Compiling test v0.1.0 (...)
Finished debug [unoptimized + debuginfo] target(s) in 0.26 secs
Running target/debug/deps/generated_test-ce82d068f4ceb10d
running 2 tests
test abc ... ok
test fooboo ... ok
答案 3 :(得分:2)
我的rstest
crate模仿pytest
语法,并提供了很大的灵活性。斐波那契示例可以非常简洁:
#[cfg(test)]
extern crate rstest;
#[cfg(test)]
mod test {
use super::*;
use rstest::rstest_parametrize;
#[rstest_parametrize(input, expected,
case(0, 0),
case(1, 1),
case(2, 1),
case(3, 2),
case(4, 3),
case(5, 5),
case(6, 8)
)]
fn fibonacci_test(input: u32, 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)
}
}
输出:
/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 test::fibonacci_test_case_0 ... ok
test test::fibonacci_test_case_1 ... ok
test test::fibonacci_test_case_2 ... ok
test test::fibonacci_test_case_4 ... ok
test test::fibonacci_test_case_5 ... ok
test test::fibonacci_test_case_3 ... ok
test test::fibonacci_test_case_6 ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
每个案例都作为一个测试案例运行。
语法简单明了,如果您需要为参数编写野生代码,则可以使用Unwrap("...everything can be valid Rust code...")
作为case
参数中的值(确定{ {1}}不是一个不错的选择,但我打算对其进行更改。
Unwrap
还支持泛型和类似rstest
的灯具。
不要忘记将pytest
添加到rstest
中的dev-dependencies
。
答案 4 :(得分:1)
以Chris Morgan’s answer为基础,这是一个用于创建参数化测试(playground)的递归宏:
macro_rules! parameterized_test {
($name:ident, $args:pat, $body:tt) => {
with_dollar_sign! {
($d:tt) => {
macro_rules! $name {
($d($d pname:ident: $d values:expr,)*) => {
mod $name {
use super::*;
$d(
#[test]
fn $d pname() {
let $args = $d values;
$body
}
)*
}}}}}}}
您可以像这样使用它:
parameterized_test!{ even, n, { assert_eq!(n % 2, 0); } }
even! {
one: 1,
two: 2,
}
parameterized_test!
定义了一个新的宏(even!
),该宏将创建带有一个参数(n
)并调用assert_eq!(n % 2, 0);
的参数化测试。
even!
的工作原理基本上类似于Chris的fib_tests!
,尽管它将测试分组到一个模块中,以便它们可以共享前缀(建议here)。此示例产生两个测试函数even::one
和even::two
。
同一语法适用于多个参数:
parameterized_test!{equal, (actual, expected), {
assert_eq!(actual, expected);
}}
equal! {
same: (1, 1),
different: (2, 3),
}
上面用于实质上逃避内部宏中美元符号的with_dollar_sign!
宏来自@durka:
macro_rules! with_dollar_sign {
($($body:tt)*) => {
macro_rules! __with_dollar_sign { $($body)* }
__with_dollar_sign!($);
}
}
我以前没有写过很多Rust宏,因此非常欢迎您提供反馈和建议。
答案 5 :(得分:1)