#test-cases #parameterized #parametrized #unit-testing #test

dev yare

为 Rust 设计的轻量级参数化测试用例库 🚀

5 个稳定版本

3.0.0 2024 年 3 月 8 日
2.0.0 2023 年 10 月 15 日
1.0.2 2022 年 8 月 19 日
1.0.1 2021 年 2 月 22 日
1.0.0 2020 年 12 月 30 日

#70 in 测试

Download history 926/week @ 2024-04-07 1102/week @ 2024-04-14 1315/week @ 2024-04-21 663/week @ 2024-04-28 1898/week @ 2024-05-05 1479/week @ 2024-05-12 1366/week @ 2024-05-19 1016/week @ 2024-05-26 1344/week @ 2024-06-02 1081/week @ 2024-06-09 1396/week @ 2024-06-16 967/week @ 2024-06-23 491/week @ 2024-06-30 599/week @ 2024-07-07 697/week @ 2024-07-14 1106/week @ 2024-07-21

2,941 每月下载量
27 crates 中使用

MIT/Apache

17KB
179

Yare ⛵

基于过程宏的参数化测试库。使用多种不同的输入运行测试用例。可以使用 'parameterized' 属性而不是 'test' 属性来定义测试用例。

特性

  • 参数化:指定不同的输入,使用单个测试定义测试多个场景。
  • 灵活:参数化测试用例的参数是表达式。
  • 开箱即用:无需任何自定义测试框架,即可与任何 Rust 版本配合使用。
  • 可重用:通过一次定义测试用例并在多个测试中使用不同参数来促进代码重用。
  • 易读:使用熟悉的 Rust 属性语法来保持可读性。
  • 可识别:每个测试用例都有一个用户定义的名称,可以引用并用于运行单个测试用例。
  • 实战检验:在 cargo-msrv、bisector 和 rust-releases 等 Crates 的测试中使用多年(等等)。

目录

示例 (返回顶部)

第一个示例

fn add5<T: Into<u32>>(component: T) -> u32 {
    component.into() + 5
}

#[cfg(test)]
mod tests {
    use super::*;
    use yare::parameterized;

    #[parameterized(
      zero_plus_five = { 0, 5 },
      one_plus_five = { 1, 6 },
      two_plus_five = { 2, 7 },
    )]
    fn test_add5(input: u16, expected: u32) {
        assert_eq!(add5(input), expected);
    }
}

带有值的示例

enum Fruit {
    Apple,
    Bramble(BrambleFruit),
    Pear,
}

trait NameOf {
    fn name_of(&self) -> &str;
}

impl NameOf for Fruit {
    fn name_of(&self) -> &str {
        match self {
            Fruit::Apple => "apple",
            Fruit::Bramble(fruit) => fruit.name_of(),
            Fruit::Pear => "pear",
        }
    }
}

enum BrambleFruit {
    Blackberry,
}

impl NameOf for BrambleFruit {
    fn name_of(&self) -> &str {
        match self {
            BrambleFruit::Blackberry => "blackberry",
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use yare::parameterized;

    #[parameterized(
      apple = { Fruit::Apple, "apple" },
      pear = { Fruit::Pear, "pear" },
      blackberry = { Fruit::Bramble(BrambleFruit::Blackberry), "blackberry" },
    )]
    fn a_fruity_test(fruit: Fruit, name: &str) {
        assert_eq!(fruit.name_of(), name)
    }
}

参数是表达式 (返回顶部)

虽然上面的参数是简单值,但任何表达式都可以用作测试用例中的参数。

示例

在下面的示例中,我们掷骰子 3 次来生成后续 roll_dice 函数调用时的种子。第一个参数 seed1 是对 roll_dice 的 函数调用。此随机函数使用值 0 进行种子化。第二个参数 seed2 是一个 块表达式。在表达式中,roll_dice 函数被调用了两次。测试本身将 seed1seed2 的最大值作为参数,掷骰子 1000 次,并检查所有值对于 d6 骰子都是有效的。

use std::sync::atomic::{AtomicU32, Ordering};
use yare::parameterized;

#[parameterized(
  seeding_randomness_with_two_dice_rolls = 
  {
    roll_dice(&AtomicU32::new(0)),                              // <- This is an expression (a function call)
    {                                                           // <- This is also an expression (a block expression)
      let from_half = roll_dice( &AtomicU32::new(u32::MAX / 2));
      let from_max = roll_dice( &AtomicU32::new(u32::MAX));
      
      u8::min(from_half, from_max)
    }
  }
)]
fn dicey(seed1: u8, seed2: u8) {
    // Creating a base seed in a complicated way for the sake of it.
    let max = u8::max(seed1, seed2);
    let seed = AtomicU32::new(u32::from(max));

    let out_of_bounds_values = (0..1000)         // roll the dice 1000 times
        .map(|_| roll_dice(&seed))
        .find(|value| !(1..=6).contains(value)); // check that the outputs of the dice are just 1, 2, 3, 4, 5 or 6. 

    assert!(out_of_bounds_values.is_none());
}

自定义测试宏(例如 tokio::test)返回顶部

默认情况下,parameterized 属性的代码生成步骤会生成带有 #[test] 属性的测试用例。例如,来自 示例add5 测试将生成类似的内容:

#[cfg(test)]
mod tests {
    use super::*;
    use yare::parameterized;

    // Approximate generated code from add5 example:
    #[cfg(test)]
    mod add5 {
        use super::*;

        #[test]
        fn zero_plus_five() {
            let input: u16 = 0;
            let expected: u32 = 5;
            assert_eq!(add5(input), expected);
        }

        #[test]
        fn one_plus_five() {
            let input: u16 = 1;
            let expected: u32 = 6;
            assert_eq!(add5(input), expected);
        }

        #[test]
        fn two_plus_five() {
            let input: u16 = 2;
            let expected: u32 = 7;
            assert_eq!(add5(input), expected);
        }
    }
}

然而,有时需要不同的测试宏。例如,当为依赖 tokio 的项目编写测试时。为此,您可能想使用 #[tokio::test](这也要求测试函数具有 async 修饰符)。

在 Yare 中,可以指定自定义测试宏。要这样做,您可以在 #[parameterized] 属性之后添加 #[test_macro(...)] 属性。

自定义测试宏示例:tokio::test

use yare::parameterized;

#[parameterized(
    zero_wait = { 0, 0 },
    show_paused = { 500, 0 },
)]
#[test_macro(tokio::test(start_paused = true))]
async fn test(wait: u64, time_elapsed: u128) {
    let start = std::time::Instant::now();
    tokio::time::sleep(tokio::time::Duration::from_millis(wait)).await;

    assert_eq!(start.elapsed().as_millis(), time_elapsed);
}

注意事项

  • #[test_macro(...)] 必须始终在 #[parameterized(...)] 属性之后指定。
  • 每个参数化测试函数只允许有一个 #[test_macro(...)] 属性。
  • 虽然可以使用导入别名重命名参数化属性(例如,use yare::parameterized as pm),但不能重命名 test_macro 属性,因为它实际上不是一个单独的宏。相反,parameterized 宏将这个属性也解析为参数。

返回类型返回顶部

Yare 支持指定参数化测试函数的返回类型。

请注意,底层测试属性也必须支持返回类型。默认情况下,Yare 会生成带有熟悉的 test 属性的独立测试用例,该属性包含在 Rust 的任何发行版中。

示例

use yare::parameterized;

#[parameterized(
  ok = { Ok(0) },
  // err = {  Err("noes!".to_string()) }, <-- enabling this would result in a failed test, since the error code will not be an `ErrorCode::Success`. See the `Termination` trait for more.
)]
fn test(value: Result<u32, String>) -> Result<(), String> {
    let v = value?;

    assert_eq!(v.unwrap(), 0);
}

函数修饰符返回顶部

Yare 支持以下功能限定符:constasyncunsafeextern。如果您使用自定义测试宏(如 tokio::test)而不是内置测试宏,这将特别有用,并与 #[parameterized(...)] 结合使用。

示例

use yare::parameterized;

#[parameterized(
  purple = { & [128, 0, 128] },
  orange = { & [255, 127, 0] },
)]
const extern "C" fn has_reds(streamed_color: &[u8]) {
    assert!(streamed_color.first().is_some());
}

全局导入参数化((回到顶部)

如果您不想在每个测试模块中导入此库(使用 use yare::parameterized;),则可以将以下代码片段放在 crate 根目录的顶部

#[cfg(test)]
#[macro_use]
extern crate yare;

许可证((回到顶部)

根据您的选择,受 Apache License, Version 2.0MIT 许可证 的许可。


除非您明确表示,否则根据 Apache-2.0 许可证定义,您提交的任何有意包含在此 crate 中的贡献,将按照上述方式双重许可,不附加任何额外条款或条件。

依赖关系

~280–730KB
~17K SLoC