#fixture #fixtures #test #unit-testing

tested-fixture

用于从测试创建固定属性的宏

2 个稳定版本

1.0.1 2024 年 5 月 3 日
1.0.0 2024 年 5 月 2 日

#176 in 测试

Download history 281/week @ 2024-04-27 264/week @ 2024-05-04 25/week @ 2024-05-11 74/week @ 2024-05-18 102/week @ 2024-05-25 112/week @ 2024-06-01 27/week @ 2024-06-08 49/week @ 2024-06-15 46/week @ 2024-06-22 5/week @ 2024-06-29 15/week @ 2024-07-06 21/week @ 2024-07-13 6/week @ 2024-07-20 8/week @ 2024-07-27 36/week @ 2024-08-03 2/week @ 2024-08-10

53 每月下载量

MIT 许可证

19KB
164

tested-fixture

Crates.io Workflow Status

用于从测试创建固定属性的宏

描述

有时一系列测试是渐进的或逐步的;也就是说一个测试建立在另一个测试之上。一个多阶段测试可能对每个步骤都有复杂的设置和验证过程,但是阶段之间有明确的边界(test_1 验证阶段 1,test_2 验证阶段 2 等.)。问题是当阶段想要共享数据时(即 test_2 想要从 test_1 停止的地方开始)。

常见的建议是在所有测试中重复所有设置代码,或者将测试组合成一个大的测试。然而,前者如果设置成本较高,会显著减慢测试速度,如果设置程序更改,还会引入显著测试维护成本。然而,后者可能导致大而难以维护的测试函数,并且当依赖跨多个文件时(即测试完整设置过程的 Foo 的单元测试难以与测试 Bar 设置过程的单元测试组合,该测试依赖于完全构建的 Foo;“组合”测试应该放在 Foo 附近还是 Bar 附近?如果测试需要访问内部以验证断言呢?)。

这个包通过允许测试返回一个可以在后续测试中使用的固定属性来提供另一种方法。测试可以通过使用单个属性宏 tested_fixture 来选择此功能。

用法

当编写类似代码的测试时

struct Foo {
    // ...
}

struct State {
    // ...
}

impl Foo {
    fn step_1() -> Self {
        Foo {
            // Complicated setup...
        }
    }

    fn step_2(&self) -> State {
        State {
            // Complicated execution...
        }
    }

    fn step_3(&self, v: &State) {
        // Complicated execution...
    }
}

重复的测试设置看起来可能如下

#[test]
fn step_1() {
    let foo = Foo::step_1();
    // Complicated assertions verify step 1...
}

#[test]
fn step_2() {
    let foo = Foo::step_1();
    // (Some?) Complicated assertions verify step 1...

    foo.step_2();
    // Complicated assertions verify step 2...
}

#[test]
fn step_3() {
    let foo = Foo::step_1();
    // (Some?) Complicated assertions verify step 1...

    let state = foo.step_2();
    // (Some?) Complicated assertions verify step 2...

    foo.step_3(&state);
    // Complicated assertions verify step 3...
}

如您所见,有很多步骤,这很容易失控。要清理它,只需将 tested_fixture 属性切换为正常 test

// Save the fixture in a static variable called `STEP_1`
#[tested_fixture::tested_fixture(STEP_1)]
fn step_1() -> Foo {
    let foo = Foo::step_1();
    // Complicated assertions verify step 1...
    foo
}

#[tested_fixture::tested_fixture(STEP_2_STATE)]
fn step_2() -> State {
    let state = STEP_1.step_2();
    // Complicated assertions verify step 2...
    state
}

#[test]
fn step_3() {
    STEP_1.step_3(&STEP_2_STATE);
    // Complicated assertions verify step 3...
}

请注意,当只运行 step_2 时,STEP_1 会在第一次访问时初始化。由于测试的顺序没有保证,这实际上也可能发生在两个测试都运行的情况下。但由于结果被缓存,即使它不是第一个运行的,step_1 测试仍然应该成功(或失败)。

高级用法

属性 tested_fixture 支持在标识符之前添加属性和可见性级别前缀,以及一个可选的 : 类型 后缀。此可选后缀可以用于返回 Result 的测试中,指定只捕获 Ok 返回值。例如:

#[tested_fixture::tested_fixture(
    /// Doc comment on the `STEP_1` global variable
    pub(crate) STEP_1: Foo
)]
fn step_1() -> Result<Foo, &'static str> {
    // ...
}

限制

普通的 #[test] 函数能够返回任何实现了 std::process::Termination 的类型,包括无限嵌套的 Result。虽然这个crate支持返回嵌套的 Result 封装,但它只支持固定深度的嵌套。此外,它不支持返回 Result 之外的任何 Termination 实现。

与所有测试相关的全局状态一样,建议测试不要修改状态,因为这样做会增加由于执行顺序或时间变化而导致测试不可靠的风险。幸运的是,这是默认行为,因为这个crate定义的所有fixture都只能通过不可变引用访问。

目前这个crate不支持异步测试。

许可证

许可协议下

依赖项

~1.4–2MB
~41K SLoC