测试
为 Clippy 开发 lint 规则是一个测试驱动开发 (TDD) 过程,因为在为新的 lint 实现任何逻辑之前,我们的首要任务是编写一些测试用例。
通过测试开发 Lint 规则
当我们开发 Clippy 时,我们会进入一个复杂而混乱的领域,其中充满了程序问题、风格错误、不合逻辑的代码以及不符合约定的情况。测试是我们用来定义何时以及在何处触发或不触发新的 lint 规则的第一层秩序。
此外,首先编写测试有助于 Clippy 开发人员为 lint 的首次迭代和进一步增强找到平衡。有了测试用例的支持,我们不必担心在 lint 的第一个版本中过度设计,也不必担心遗漏 lint 的一些明显的边缘情况。这种方法使我们能够迭代地增强每个 lint。
Clippy UI 测试
我们在 Clippy 中使用 UI 测试进行测试。这些 UI 测试检查 Clippy 的输出是否与我们期望的完全一致。每个测试都只是一个普通的 Rust 文件,其中包含我们要检查的代码。
Clippy 的输出会与一个 .stderr
文件进行比较。请注意,您不必自己创建此文件。我们将使用 cargo bless
命令(稍后会看到)来生成 .stderr
文件。
编写测试用例
现在让我们考虑一下我们虚构的 foo_functions
lint 的一些测试。我们首先打开由 cargo dev new_lint
创建的测试文件 tests/ui/foo_functions.rs
。
使用一些示例更新文件以开始
#![warn(clippy::foo_functions)] // < Add this, so the lint is guaranteed to be enabled in this file // Impl methods struct A; impl A { pub fn fo(&self) {} pub fn foo(&self) {} //~ ERROR: function called "foo" pub fn food(&self) {} } // Default trait methods trait B { fn fo(&self) {} fn foo(&self) {} //~ ERROR: function called "foo" fn food(&self) {} } // Plain functions fn fo() {} fn foo() {} //~ ERROR: function called "foo" fn food() {} fn main() { // We also don't want to lint method calls foo(); let a = A; a.foo(); }
如果没有实际的 lint 逻辑在看到 foo
函数名时发出 lint,此测试只会通过,因为不会发出任何 lint。但是,我们现在可以使用以下命令运行测试
$ TESTNAME=foo_functions cargo uitest
Clippy 将编译并得出测试结果为 ok
的结论
...Clippy warnings and test outputs...
test compile_test ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.48s
这是正常的。毕竟,我们编写了一堆 Rust 代码,但我们还没有真正实现任何让 Clippy 检测 foo
函数并发出 lint 的逻辑。
当我们逐步实现我们的 lint 逻辑时,我们将继续运行此 UI 测试命令。Clippy 将开始输出信息,使我们能够检查输出是否正在变成我们想要的样子。
示例输出
当我们的 foo_functions
lint 被测试时,输出将如下所示
failures:
---- compile_test stdout ----
normalized stderr:
error: function called "foo"
--> tests/ui/foo_functions.rs:6:12
|
LL | pub fn foo(&self) {}
| ^^^
|
= note: `-D clippy::foo-functions` implied by `-D warnings`
error: function called "foo"
--> tests/ui/foo_functions.rs:13:8
|
LL | fn foo(&self) {}
| ^^^
error: function called "foo"
--> tests/ui/foo_functions.rs:19:4
|
LL | fn foo() {}
| ^^^
error: aborting due to 3 previous errors
请注意片段顶部的 failures 标签,我们将在下一节中删除它(保存此输出)。
注意:您可以通过指定逗号分隔的列表来运行多个测试文件:
TESTNAME=foo_functions,bar_methods,baz_structs
。
cargo bless
一旦我们对输出感到满意,我们需要运行此命令来为我们的 lint 生成或更新 .stderr
文件
$ TESTNAME=foo_functions cargo uibless
这会将发出的 lint 建议和修复写入 .stderr
文件,其中包含 lint 的原因、建议的修复以及行号等。
然后,运行 TESTNAME=foo_functions cargo uitest
应该会通过。当我们提交我们的 lint 时,我们也需要提交生成的 .stderr
文件。
一般来说,您应该只提交由 cargo bless
更改的特定 lint 文件,您正在创建/编辑这些文件。
注意:如果生成的
.stderr
和.fixed
文件为空,则应将其删除。
toml
测试
某些 lint 可以通过 clippy.toml
文件进行配置。这些配置值在 tests/ui-toml
中进行测试。
要在此处添加新测试,请创建一个新目录并添加文件
clippy.toml
:在此处放置您要测试的配置值。lint_name.rs
:一个测试文件,您可以在其中放置测试代码,该代码应根据clippy.toml
文件中设置的配置看到不同的 lint 行为。
可以使用 cargo bless
再次生成潜在的 .stderr
和 .fixed
文件。
Cargo Lints
Cargo lint 的测试过程有所不同,因为现在我们对 Cargo.toml
清单文件感兴趣。在这种情况下,我们还需要一个与该清单关联的最小 crate。这些测试在 tests/ui-cargo
中生成。
假设我们有一个名为 foo_categories
的新示例 lint,我们可以运行
$ cargo dev new_lint --name=foo_categories --pass=late --category=cargo
运行 cargo dev new_lint
后,我们将默认找到两个新的 crate,每个 crate 都有其清单文件
tests/ui-cargo/foo_categories/fail/Cargo.toml
:此文件应导致新的 lint 引发错误。tests/ui-cargo/foo_categories/pass/Cargo.toml
:此文件不应触发 lint。
如果您需要更多的情况,您可以复制其中一个 crate(在 foo_categories
下)并重命名它。
生成 .stderr
文件的过程与其他 lint 相同,并且将 TESTNAME
变量前置到 cargo uitest
也适用于 Cargo lint。
Rustfix 测试
如果您正在处理的 lint 正在使用结构化建议,则 rustfix
将 lint 中的建议应用于测试文件代码,并将其与 .fixed
文件的内容进行比较。
结构化建议告诉用户如何修复或重写已使用 span_lint_and_sugg
lint 的某些代码。
如果应使用 span_lint_and_sugg
生成建议,但并非所有建议都导致有效的代码,您可以使用测试文件顶部的 //@no-rustfix
注释,以不在该文件上运行 rustfix
。
我们将在 后续章节 中更深入地讨论建议。
运行测试后,使用 cargo bless
自动生成 .fixed
文件。
手动测试
如果您添加了一些 println!
并且测试套件输出变得难以阅读,则针对示例文件进行手动测试可能会很有用。
要使用您本地的修改尝试 Clippy,请从工作副本根目录运行。
$ cargo dev lint input.rs