5个稳定版本
1.2.0 | 2023年12月5日 |
---|---|
1.1.0 | 2023年7月22日 |
1.0.2 | 2023年4月12日 |
1.0.1 | 2023年4月10日 |
1.0.0 | 2022年2月25日 |
#35 在 测试 中
2,648 每月下载量
用于 6 crates
145KB
3.5K SLoC
Trybuild2
trybuild2
是 trybuild 的分支,允许使用内联测试。
Trybuild 是一个测试框架,用于调用 rustc 对一组测试用例进行测试,并断言任何产生的错误信息都是预期的。
这种测试通常用于测试涉及过程宏的错误报告。我们会编写测试用例来触发宏检测到的错误或Rust编译器在展开代码中检测到的错误,并将它们与预期的错误进行比较,以确保它们仍然友好。
这种测试风格有时被称为 ui测试,因为它们测试用户与库交互的方面,而普通API测试则不会涵盖这些方面。
这里没有任何内容是针对宏的;trybuild2 也可以用于测试非宏API的误用。
[dev-dependencies]
trybuild2 = "1.0"
编译器支持:需要 rustc 1.45+
编译失败测试
一个最小化的 trybuild 配置看起来像这样
#[test]
fn ui() {
let t = trybuild2::TestCases::new();
t.compile_fail("tests/ui/*.rs");
}
可以使用 cargo test
运行测试。它将单独编译与glob模式匹配的每个源文件,期望它们编译失败,并断言编译器的错误信息与相邻命名的 *.stderr 文件中的预期输出匹配(与测试文件同名,但扩展名不同)。如果匹配,则测试用例被认为是成功的。
在项目的 Cargo.toml 中的 [dev-dependencies] 下列出的依赖项可以在测试用例中访问。
失败的测试会显示预期和实际的编译器输出。
未能编译失败的编译失败测试也是失败。
要测试单个源文件,请使用
cargo test -- ui trybuild2=example.rs
其中 ui
是调用 trybuild2
的函数 #[test]
的名称,而 example.rs
是要测试的文件名。
通过测试
相同的测试框架也能够运行预期会通过的测试。通常你只需直接使用 Cargo 运行这些测试,但能够这样结合模式可能会在需要参与者逐个测试用例的工作坊中很有用。Trybuild 最初是为我在 Rust Latam 的过程宏工作坊开发的。
#[test]
fn ui() {
let t = trybuild2::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 文件不存在,测试运行器会将实际的编译器输出保存到包含 Cargo.toml 的目录下名为 wip 的目录中。因此,你可以通过删除这些文件,运行 cargo test
,并将 wip 中的所有文件移动到你的测试用例目录中来更新这些文件。
或者,使用环境变量 TRYBUILD2=overwrite
来运行带有 cargo test
的命令来跳过 wip 目录,并将所有编译器输出直接写入。之后,你将想要检查 git diff
以确保编译器的输出符合你的预期。
要测试什么
对于 compile-fail 测试,为任何你希望知道用户界面编译器输出有变化的情况编写测试。作为一个负面例子,请不要简单地编写调用所有公共 API 的测试用例,参数类型错误;这没有任何好处。
一个常见的用途是测试过程宏发出的特定错误消息。例如,来自 ref-cast
crate 的 derive 宏需要放置在具有 #[repr(C)]
或 #[repr(transparent)]
的类型上,以确保展开时没有未定义的行为,它在编译时强制执行
error: RefCast trait requires #[repr(C)] or #[repr(transparent)]
--> $DIR/missing-repr.rs:3:10
|
3 | #[derive(RefCast)]
| ^^^^^^^
消耗辅助属性的宏想要检查这些属性中未识别的内容是否正确地通知了调用者。错误消息是否正确放置在错误标记下,而不是在无用的 call_site span 上?
error: unknown serde field attribute `qqq`
--> $DIR/unknown-attribute.rs:5:13
|
5 | #[serde(qqq = "...")]
| ^^^
声明式宏也可以从 compile-fail 测试中受益。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
crate引入了只读但公开的结构体字段,即使调用者有对周围结构的 &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
在这些所有情况下,编译器的输出可能会改变,因为我们的 crate 或我们的依赖之一破坏了某些东西,或者由于 Rust 编译器的变化所致。这两者都是拥有精心设计的编译失败测试的良策。如果我们重构并错误地导致了一个原本正确的错误现在不再发出或发出在错误的位置,这对于测试套件捕获来说很重要。如果编译器更改了某些东西,使得我们关心的错误消息变得实质上更糟糕,那么捕获并报告为编译器问题也是非常重要的。
许可证
根据您选择,许可协议为Apache License, Version 2.0或MIT license。除非您明确声明,否则根据Apache-2.0许可证定义的,您有意提交以包含在此crate中的任何贡献,都将按上述方式双重许可,没有任何附加条款或条件。
依赖关系
~1–9.5MB
~80K SLoC