#fixture #snapshot #fixtures #input-file

fn-fixture

一个用于快速生成测试夹具快照的过程宏

3个稳定版本

1.0.2 2021年2月7日
1.0.1 2020年8月2日

#748测试

Download history 72/week @ 2024-03-31 7/week @ 2024-04-07 31/week @ 2024-04-14 45/week @ 2024-04-21 28/week @ 2024-04-28 14/week @ 2024-05-05 1/week @ 2024-05-12 36/week @ 2024-05-19 19/week @ 2024-05-26 16/week @ 2024-06-02 45/week @ 2024-06-09 5/week @ 2024-06-16 51/week @ 2024-06-23 2/week @ 2024-06-30 6/week @ 2024-07-07 7/week @ 2024-07-14

66 每月下载量
dst 中使用

MIT 许可证

21KB
406

fn-fixture

此包提供了一种易于使用的注释,用于测试创建。每个夹具有三个方面

  • 一个接受输入并具有实现 std::fmt::Debug 返回类型的函数。

  • 一个子文件夹文件夹(可以进一步嵌套,不需要一致),每个以树终止的文件夹都包含一个与函数输入类型相对应的单个输入文件(.rs .txt .bin)。

  • 预期结果,如果不存在,则自动生成。可以将panic视为一个有效的可能预期结果。

此项目遵循惯例而不是配置。输入文件简单地命名: input.rs input.txt input.bin。预期输出文件以夹具名称命名。这意味着多个夹具可以共享测试文件夹树。您不需要自己创建输出文件;如果不存在预期结果,将会自动生成一个。

Crates.io version docs.rs status Crates.io license Github Tests

使用方法

[dev-dependencies]
fn-fixture = "1.0.0"

自给自足

此项目使用自身来测试自身,这使其成为示例和技术解释的三重用途。

snapshot-tests 有四个测试树

  • source 树解释了测试是如何生成的。这是测试 fn-fixture 的主要方法。这些测试引用其他两个测试树。

  • code 树提供了当将测试输入到恒等函数时测试会做什么的示例。这是测试 fn-fixture 的次要方法。

  • bad 树提供了不正确构建的测试示例,这些测试会导致编译错误。这些测试不是直接由 fn-fixture 进行测试的,而是使用预期的编译失败宏在 source/bads 中使用。

  • example 树遵循下面的直接示例。

self_snapshots.rs 是使用 fn-fixture 运行这些测试的文件。

示例

此示例是项目的一部分,用于生成和执行。要查看代码生成过程,请参考 snapshot-tests/source/examples。要查看文件结构,请参考 snapshot-tests/examples

制作测试

此测试从文本文件中解析数字

#[fn_fixture::snapshot("snapshot-tests/examples")]
fn parse_unsigned_number(value: &str) -> Result<usize, impl std::fmt::Debug> {
    value.parse()
}

#[fn_fixture::snapshot("snapshot-tests/examples")]
fn parse_signed_number(value: &str) -> Result<isize, impl std::fmt::Debug> {
    value.parse()
}

快照-测试/示例/错误的数字:

forty two

快照-测试/示例/正确的数字:

42

快照-测试/示例/有时是数字:

-42

初次运行测试

首次运行时,cargo test 将有 6 个测试(2 个固定值,每个 3 个快照)如下所示

test parse_signed_number::sometimes_number ... FAILED
test parse_signed_number::good_number ... FAILED
test parse_unsigned_number::sometimes_number ... FAILED
test parse_unsigned_number::good_number ... FAILED
test parse_unsigned_number::bad_number ... FAILED
test parse_signed_number::bad_number ... FAILED

IntelliJ 会自动为您结构化测试

IntelliJ Test Results

具体的错误将如下所示

thread 'parse_unsigned_number::bad_number' panicked at 'No expected value set: ... 

每个快照文件夹,bad_number good_numbersometimes_number,现在都将有 parse_signed_number.actual.txtparse_unsigned_number.actual.txt

bad_number 的结果将具有

Ok(
    Err(
        ParseIntError {
            kind: InvalidDigit,
        },
    ),
)

good_number 的结果将具有

Ok(
    Ok(
        42,
    ),
)

sometimes_number 将分别匹配 parse_unsigned_numberparse_signed_number 的结果。

  • 注意,最外层的 Ok( 表示线程没有恐慌。如果您期望恐慌,则最外层应为 Err(

最后,您需要手动审查每个 .actual 文件。如果文件正确,则删除 .actual。如果不正确,则继续修改代码并运行测试;.actual 将在每个运行中用结果覆盖。

之后

在某些时候,您的输出可能会改变。例如,如果 shapshot-tests/examples/bad_number/parse_unsigned_number.txt 中的一个字符改变(如手动删除期望输出中的 i),则测试结果将如下所示

---- parse_unsigned_number::bad_number stdout ----
thread 'parse_unsigned_number::bad_number' panicked at 'assertion failed: `(left == right)`
  left: `"Ok(\n    Err(\n        ParseIntError {\n            kind: InvalidDigit,\n        },\n    ),\n)\n"`,
 right: `"Ok(\n    Err(\n        ParseIntError {\n            kind: InvaldDigit,\n        },\n    ),\n)\n"`', fn-fixture\tests\self_snapshots.rs:19:1

内部

此示例的生成代码将如下所示(简化/改写以删除一些边缘情况处理和样板代码)

fn parse_unsigned_number(value: &str, expected_file: &str) {
    fn parse_unsigned_number(value: &str) -> Result<usize, impl std::fmt::Debug> {
        value.parse()
    }
    // omitted logic for panic-handling
    // omitted logic for writing actual file instead
    assert_eq!(
        format!("{:#?}\n", parse_unsigned_number(value)),
        File::read_to_string(expected_file),
    );
}
mod parse_unsigned_number {
    #[test] fn bad_number() { super::parse_unsigned_number(include_str!("snapshot-tests/examples/bad_number/input.txt"), "snapshot-tests/examples/bad_number/parse_unsigned_number.txt") }
    #[test] fn good_number() { /* ... */ }
    #[test] fn sometimes_number() { /* ... */ }
}

fn parse_signed_number(value: &str, expected_file: &str) {
    fn parse_signed_number(value: &str) -> Result<isize, impl std::fmt::Debug> {
        value.parse()
    }
    // omitted logic for panic-handling
    // omitted logic for writing actual file instead
    assert_eq!(
        format!("{:#?}\n", parse_signed_number(value)),
        File::read_to_string(expected_file),
    );
}
mod parse_signed_number {
    #[test] fn bad_number() { super::parse_signed_number(include_str!("snapshot-tests/examples/bad_number/input.txt"), "snapshot-tests/examples/bad_number/parse_signed_number.txt") }
    #[test] fn good_number() { /* ... */ }
    #[test] fn sometimes_number() { /* ... */ }
}

输入类型

  • input.txt 对应于 include_str!(...)
  • input.bin 对应于 include_bytes!(...)
  • input.rs 对应于 include!(...)
    • input.rs 获得的类型可以是任何适合于调用 fixture 的单参数的任何类型。 snapshot-tests/code 有很多使用 Rust 代码作为输入的例子。

限制

  • fixture 的名称不能为 input。那将意味着期望的输出是 input.txt;请将其称为 quine

  • 预期的 panic 应该是 String&str。这很少,如果不是从未出现过问题。每个已知的库都使用这些。例如,panic!("At the disco") 是一个 &strunwrap()/expect(...) 使用 String

  • 多行字符串输出应该包裹在 .lines().collect::<Vec<String>>() 中。这些测试是为了让人审查的。IntelliJ 会帮你进行差异比较。

  • 每个终止目录(没有子目录的目录)必须恰好有一个 input 文件。

  • 有子目录的目录不得有 input 文件。

  • 引用的文件夹是顶级文件夹,而不是一个测试本身。

  • 在不修改包含文件或清除编译器缓存的情况下添加新测试将被忽略。这是一个编译器级别的限制。

  • 树中的每个文件夹都必须是一个有效的 Rust 标识符。这些是嵌套测试模块的命名方式。

  • 不建议将其他文件添加到文件夹中,未来版本可能会将它们视为错误。

  • 返回类型必须实现 std::fmt::Debug

  • 不支持注释字段,即使它可调用。

提示

  • impl std::fmt::Debug 作为返回类型。

  • 为输出的一部分使用不同的函数;不要有一个函数以两种不同的方式解析输入。

  • 尽管这个库支持 panic 作为期望的输出,但测试这种行为是不被鼓励的。

  • 将您的目录命名为 snapshot-tests。Snapshot 解释了测试的类型,IDE 可能会识别以 -tests 结尾的目录。

  • 泛型适用于不同类型的多个 input.rs 文件。

许可证

此应用程序是从内部开发的工具派生出来的,因此根据 MIT 许可证发布

依赖项

~1.5MB
~37K SLoC