#fixture #macro #unit-testing #fixtures #test

tested-fixture-macros

tested-fixture 的 proc-macro

1 个稳定版本

1.0.0 2024 年 5 月 2 日

#15#fixture

Download history 176/week @ 2024-04-27 233/week @ 2024-05-04 26/week @ 2024-05-11 78/week @ 2024-05-18 106/week @ 2024-05-25 115/week @ 2024-06-01 29/week @ 2024-06-08 51/week @ 2024-06-15 51/week @ 2024-06-22 5/week @ 2024-06-29 17/week @ 2024-07-06 23/week @ 2024-07-13 11/week @ 2024-07-20 10/week @ 2024-07-27 41/week @ 2024-08-03 3/week @ 2024-08-10

66 每月下载量
用于 tested-fixture

MIT 许可证

7KB
111

tested-fixture

Crates.io Workflow Status

用于从测试创建设置项的属性宏

描述

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

常见的建议是复制所有设置代码到所有测试中,或者将测试组合成一个大的测试。然而,如果设置成本高昂,前者会显著减慢测试速度,如果设置过程更改,还会带来重大的测试维护成本。后者可能会产生大型且难以维护的测试函数,并且无法解决跨多个文件依赖的问题(即测试 Foo 完整设置过程的单元测试难以与测试 Bar 设置过程的单元测试结合;这个“组合”测试应该放在 Foo 还是 Bar 附近?如果测试需要访问内部结构以验证断言呢?)。

此 crate 通过允许测试返回一个可用于后续测试的设置项来提供一种替代方法。测试可以通过使用单个属性宏 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 属性支持属性和标识符前的可见性级别前缀,以及一个可选的 : type 后缀。此可选后缀可用于返回 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
~40K SLoC