98 个稳定版本
1.0.99 | 2024 年 7 月 31 日 |
---|---|
1.0.96 | 2024 年 5 月 14 日 |
1.0.91 | 2024 年 3 月 30 日 |
1.0.86 | 2023 年 12 月 20 日 |
1.0.10 | 2019 年 7 月 30 日 |
#1 in 测试
526,219 每月下载量
用于 1,165 个 Crates (1,052 直接)
145KB
3.5K SLoC
Trybuild
Trybuild 是一个用于调用 rustc 并对一组测试用例执行测试的测试套件,并断言任何生成的错误消息都是预期中的。
此类测试通常用于测试涉及过程宏的错误报告。我们会编写测试用例来触发宏检测到的错误或 Rust 编译器在展开代码中检测到的错误,并将它们与预期错误进行比较,以确保它们保持友好。
这种测试风格有时被称为 ui tests,因为它们测试用户与库交互的方面,而这些方面不会在常规 API 测试中得到覆盖。
这里没有特定于宏的内容;trybuild 同样适用于测试非宏 API 的误用。
[dev-dependencies]
trybuild = "1.0"
编译失败测试
最小的 trybuild 设置看起来像这样
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/*.rs");
}
测试可以通过以下命令运行: cargo test
。它将单独编译与 glob 模式匹配的每个源文件,期望它们无法编译,并断言编译器的错误消息与相邻命名的 *.stderr 文件(文件名与测试相同,但扩展名不同)中的预期输出相匹配。如果匹配,则测试用例被认为是成功的。
项目 Cargo.toml 下的 [dev-dependencies]
中列出的依赖项在测试用例中可访问。
失败的测试将显示预期与实际的编译器输出。
未能失败编译的编译失败测试也是失败。
要测试单个源文件,请使用
cargo test -- ui trybuild=example.rs
其中 ui
是调用 trybuild
的 #[test]
函数的名称,而 example.rs
是要测试的文件名称。
通过测试
相同的测试框架也可以运行预期会通过的测试。通常你只需直接使用 Cargo 运行这些测试,但能够以这种方式组合模式可能对研讨会很有用,在这些研讨会上,参与者一次一个地完成测试案例。Trybuild 最初是为我在 Rust Latam 的 过程宏研讨会 开发的。
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.pass("tests/01-parse-header.rs");
t.pass("tests/02-parse-body.rs");
t.compile_fail("tests/03-expand-four-errors.rs");
t.pass("tests/04-paste-ident.rs");
t.pass("tests/05-repeat-section.rs");
//t.pass("tests/06-make-work-in-function.rs");
//t.pass("tests/07-init-array.rs");
//t.compile_fail("tests/08-ident-span.rs");
}
如果测试成功编译并且编译后的二进制文件执行时没有 panic,则认为通过测试。
详细信息
这是整个 API。
工作流程
有两种方式可以在迭代测试案例或库时更新 *.stderr 文件;不建议手动编写。
首先,如果测试案例正在以 compile_fail 运行,但相应的 *.stderr 文件不存在,测试运行器将保存实际的编译器输出,并将其放入名为 wip 的目录中(在包含 Cargo.toml 的目录内)。因此,您可以通过删除这些文件,运行 cargo test
,并将所有文件从 wip 移动到测试案例目录中来更新这些文件。
或者,使用环境变量 TRYBUILD=overwrite
运行 cargo test
,以跳过 wip 目录并将所有编译器输出直接写入原位。运行后,您需要检查 git diff
,以确保编译器的输出符合预期。
要测试什么
当涉及到 compile-fail 测试时,为任何关心用户界面编译器输出变化的更改编写测试。作为一个负面示例,请不要简单地编写只调用所有公共 API 的测试,并传递错误类型的参数;这样不会有任何好处。
一个常见的用途是测试过程宏生成的特定错误信息。例如,来自 ref-cast
crate 的 derive 宏要求放置在具有 #[repr(C)]
或 #[repr(transparent)]
的类型上,以使展开无 undefined behavior,它在编译时强制执行。
error: RefCast trait requires #[repr(C)] or #[repr(transparent)]
--> $DIR/missing-repr.rs:3:10
|
3 | #[derive(RefCast)]
| ^^^^^^^
消耗辅助属性的宏希望检查这些属性中的未知内容是否正确地通知了调用者。错误信息是否正确地放置在错误的标记下,而不是无用的 call_site 范围内?
error: unknown serde field attribute `qqq`
--> $DIR/unknown-attribute.rs:5:13
|
5 | #[serde(qqq = "...")]
| ^^^
声明性宏也能从编译失败测试中受益。serde_json 中的 json!
宏是一个巨大的 macro_rules 宏,但努力确保来自输入中损坏的 JSON 的错误消息始终出现在最合适的标记上。
error: no rules expected the token `,`
--> $DIR/double-comma.rs:4:38
|
4 | println!("{}", json!({ "k": null,, }));
| ^ no rules expected this token in macro call
有时我们可能有一个宏可以成功展开,但我们期望它在宏展开之后的某个时刻触发特定的编译器错误。例如,readonly
库引入了只读但可公开访问的结构体字段,即使调用者有对周围结构体的 &mut 引用也是如此。如果有人向只读字段写入,我们需要确保它不会编译。
error[E0594]: cannot assign to data in a `&` reference
--> $DIR/write-a-readonly.rs:17:26
|
17 | println!("{}", s.n); s.n += 1;
| ^^^^^^^^ cannot assign
在这些所有情况下,编译器的输出可能会改变,因为我们或我们的依赖项破坏了某些东西,或者由于 Rust 编译器的变化。这两个都是拥有良好的编译失败测试的好理由。如果我们重构并错误地导致原本正确的错误现在不再发出,或者错误地发现在错误的位置,这对测试套件来说是很重要的。如果编译器改变了某些东西,使我们所关心的错误信息大大恶化,这也同样重要,需要捕获并报告为编译器问题。
许可证
根据您的选择,在以下任一许可下获得许可:Apache License, Version 2.0 或 MIT 许可证。除非您明确声明,否则根据 Apache-2.0 许可证定义的,您有意提交以包含在此存储库中的任何贡献,都应如上所述双重许可,不附加任何额外条款或条件。
依赖项
~1–9MB
~80K SLoC