如何编写测试
测试是 Rust 函数,用于验证非测试代码是否在 预期的方式。测试函数的主体通常执行这三个 行动:
- 设置任何需要的数据或状态。
- 运行要测试的代码。
- 断言结果是您所期望的。
让我们看看 Rust 专门为编写测试提供的功能,这些测试
执行这些作,包括test
属性、一些宏和should_panic
属性。
测试函数剖析
简单来说,Rust 中的测试是一个用test
属性。属性是关于 Rust 代码片段的元数据;一个例子是
这derive
属性。更改函数
添加到测试函数中,添加#[test]
在之前的线路上fn
.当您运行
testing 替换为cargo test
命令,Rust 会构建一个运行
带注释的函数,并报告每个测试函数是否通过 或
失败。
每当我们使用 Cargo 创建一个新的库项目时,一个带有 test 函数是自动生成的。此模块为您提供一个 模板来编写测试,因此您不必查找确切的 结构和语法。您可以添加任意数量的 额外的测试功能和任意数量的测试模块!
我们将通过试验模板来探索测试工作原理的一些方面 test 之前。然后我们将编写一些实际测试 调用我们编写的一些代码,并断言其行为是正确的。
让我们创建一个名为adder
这将添加两个数字:
$ cargo new adder --lib
Created library `adder` project
$ cd adder
src/lib.rs 文件的内容在adder
库应如下所示
示例 11-1.
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
cargo new
现在,我们只关注it_works
功能。请注意#[test]
annotation:此属性表示这是一个测试函数,因此 test
Runner 知道将此函数视为测试。我们也可能有 non-test
函数中的tests
模块来帮助设置常见场景或执行
common operations 的 common作,所以我们总是需要指出哪些函数是 test。
示例函数体使用assert_eq!
宏来断言result
,
,其中包含 2 和 2 相加的结果,等于 4。此断言的作用为
典型测试的格式示例。让我们运行它,看看这个测试
通过。
这cargo test
command 运行我们项目中的所有测试,如 清单 所示
11-2.
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.57s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Cargo 编译并运行了测试。我们看到了这条线running 1 test
.下一个
line 显示生成的测试函数的名称,名为tests::it_works
,
并且运行该测试的结果是ok
.总体总结test result: ok.
表示所有测试都通过,并且读取1 passed; 0 failed
通过或失败的测试总数。
可以将测试标记为 ignored ,这样它就不会在特定的
实例;我们将在“忽略某些测试,除非特别
requested“部分。因为我们
没有在这里这样做,摘要显示0 ignored
.
这0 measured
statistic 用于衡量性能的基准测试。
在撰写本文时,基准测试仅在 nightly Rust 中可用。请参阅有关基准测试的文档以了解更多信息。
我们可以将参数传递给cargo test
命令仅运行
name 匹配字符串;这称为筛选,我们将在 “按名称运行测试子集” 一节中介绍。在这里,我们
尚未筛选正在运行的测试,因此摘要的末尾显示0 filtered out
.
测试输出的下一部分从Doc-tests adder
用于
任何文档测试的结果。我们还没有任何文档测试,
但 Rust 可以编译出现在我们 API 文档中的任何代码示例。
此功能有助于使您的文档和代码保持同步!我们将讨论如何
在 “Documentation Comments as (文档注释) 中编写文档测试
Tests“部分。现在,我们将
忽略Doc-tests
输出。
让我们开始根据自己的需要自定义测试。首先,更改
这it_works
函数设置为其他名称,例如exploration
这样:
文件名: src/lib.rs
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exploration() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
然后运行cargo test
再。输出现在显示exploration
而不是it_works
:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.59s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::exploration ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
现在我们将添加另一个测试,但这次我们将进行一个失败的测试!测试
fail 当 test 函数中的某些内容出现 panic 时。每个测试都在一个新的
线程,当主线程看到测试线程已死亡时,测试为
标记为失败。在第 9 章中,我们讨论了如何消除 panic 的最简单方法
调用panic!
宏。将新测试作为名为another
,所以你的 src/lib.rs 文件看起来像示例 11-3。
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exploration() {
let result = add(2, 2);
assert_eq!(result, 4);
}
#[test]
fn another() {
panic!("Make this test fail");
}
}
panic!
宏使用cargo test
.输出应类似于 Listing
11-4,这表明我们的exploration
test passed 和another
失败。
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.72s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 2 tests
test tests::another ... FAILED
test tests::exploration ... ok
failures:
---- tests::another stdout ----
thread 'tests::another' panicked at src/lib.rs:17:9:
Make this test fail
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::another
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
而不是ok
、该线test tests::another
显示FAILED
.两个新
各个部分出现在各个结果和摘要之间:第一个
显示每个测试失败的详细原因。在这种情况下,我们得到
详细信息another
失败是因为它panicked at 'Make this test fail'
上
src/lib.rs 文件中的第 17 行。下一部分仅列出所有
失败的测试,这在存在大量测试和大量
详细的失败测试输出。我们可以使用失败测试的名称来运行
that test 以更轻松地调试它;我们将更多地讨论在
“控制测试的运行方式”部分。
摘要行显示在末尾:总体而言,我们的测试结果为FAILED
.我们
有 1 次测试通过和 1 次测试失败。
现在,您已经了解了不同场景中的测试结果,
让我们看看panic!
在测试中很有用。
使用assert!
宏
这assert!
宏,当您需要时非常有用
确保测试中的某些 condition 的计算结果为true
.我们给出assert!
macro 是一个计算结果为 Boolean 的参数。如果值为true
,则什么都没有发生,测试通过。如果值为false
这assert!
宏调用panic!
导致测试失败。使用assert!
macro 帮助我们检查代码是否按预期运行。
在第 5 章示例 5-15 中,我们使用了Rectangle
struct 和can_hold
方法,在示例 11-5 中重复。让我们把这段代码放在 src/lib.rs 文件中,然后使用assert!
宏。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
Rectangle
struct 及其can_hold
第 5 章中的方法这can_hold
method 返回一个 Boolean,这意味着这是一个完美的用例
对于assert!
宏。在示例 11-6 中,我们编写了一个测试,该测试执行can_hold
方法,方法是创建一个Rectangle
宽度为 8 且
高度为 7 并断言它可以容纳另一个Rectangle
实例,该实例
宽度为 5,高度为 1。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn larger_can_hold_smaller() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
assert!(larger.can_hold(&smaller));
}
}
can_hold
检查较大的 Rectangle 是否确实可以容纳较小的 Rectangle请注意use super::*;
行内的tests
模块。这tests
module 为
一个常规模块,遵循我们在 章节 中介绍的通常可见性规则
7 在“引用模块中项目的路径”
Tree“部分。因为tests
module 是一个内部的 module,我们需要带来
外部模块中的待测试代码添加到内部模块的范围内。我们使用
一个 glob,所以我们在外部模块中定义的任何东西都可以用于 thistests
模块。
我们已将我们的测试命名为larger_can_hold_smaller
,我们创建了两个Rectangle
实例。然后我们调用assert!
macro 和
将调用larger.can_hold(&smaller)
.此表达式为
应该返回true
,因此我们的测试应该通过。让我们来了解一下!
$ cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
running 1 test
test tests::larger_can_hold_smaller ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests rectangle
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
它确实通过了!让我们添加另一个测试,这次断言一个较小的 rectangle 无法容纳更大的 rectangle:
文件名: src/lib.rs
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn larger_can_hold_smaller() {
// --snip--
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
assert!(larger.can_hold(&smaller));
}
#[test]
fn smaller_cannot_hold_larger() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
assert!(!smaller.can_hold(&larger));
}
}
因为can_hold
函数是false
,
我们需要在将该结果传递给assert!
宏。作为
result,如果can_hold
返回false
:
$ cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
running 2 tests
test tests::larger_can_hold_smaller ... ok
test tests::smaller_cannot_hold_larger ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests rectangle
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
通过两项测试!现在让我们看看当我们
在我们的代码中引入一个 bug。我们将更改can_hold
方法,将大于号替换为小于号
比较宽度:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
// --snip--
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width < other.width && self.height > other.height
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn larger_can_hold_smaller() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
assert!(larger.can_hold(&smaller));
}
#[test]
fn smaller_cannot_hold_larger() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
assert!(!smaller.can_hold(&larger));
}
}
现在运行测试会产生以下结果:
$ cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
running 2 tests
test tests::larger_can_hold_smaller ... FAILED
test tests::smaller_cannot_hold_larger ... ok
failures:
---- tests::larger_can_hold_smaller stdout ----
thread 'tests::larger_can_hold_smaller' panicked at src/lib.rs:28:9:
assertion failed: larger.can_hold(&smaller)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::larger_can_hold_smaller
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
我们的测试发现了错误!因为larger.width
是8
和smaller.width
是5
中,比较can_hold
现在返回false
: 8 不是
小于 5。
使用assert_eq!
和assert_ne!
宏
验证功能的一种常见方法是测试结果之间的相等性
)和您希望代码返回的值。你可以
通过使用assert!
宏,并使用运算符向其传递表达式。但是,这是一个非常常见的测试,标准库
提供一对宏 -==
assert_eq!
和assert_ne!
- 执行此测试
更方便。这些宏比较两个参数是否相等或
不等式。如果断言
fails 的 Fails,这样更容易看到测试失败的原因;相反,assert!
macro 仅表示它获得了false
值,而不打印导致==
false
价值。
在示例 11-7 中,我们编写了一个名为add_two
这增加了2
到其
参数,那么我们使用assert_eq!
宏。
pub fn add_two(a: usize) -> usize {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_adds_two() {
let result = add_two(2);
assert_eq!(result, 4);
}
}
add_two
使用assert_eq!
宏让我们检查一下它是否通过!
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::it_adds_two ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
我们创建一个名为result
,它保存调用add_two(2)
.然后我们传递result
和4
作为参数,将assert_eq!
.
此测试的输出行为test tests::it_adds_two ... ok
和ok
text 表示我们的测试通过!
让我们在代码中引入一个 bug,看看是什么assert_eq!
看起来当它
失败。更改add_two
函数来添加3
:
pub fn add_two(a: usize) -> usize {
a + 3
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_adds_two() {
let result = add_two(2);
assert_eq!(result, 4);
}
}
再次运行测试:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::it_adds_two ... FAILED
failures:
---- tests::it_adds_two stdout ----
thread 'tests::it_adds_two' panicked at src/lib.rs:12:9:
assertion `left == right` failed
left: 5
right: 4
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::it_adds_two
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
我们的测试发现了错误!这it_adds_two
test failed,并且消息告诉
我们assertion `left == right` failed
以及left
和right
值
是。此消息可帮助我们开始调试:left
参数,而我们
调用add_two(2)
是5
但是right
argument 是4
.
您可以想象,当我们有很多
测试正在进行中。
请注意,在某些语言和测试框架中,相等的参数
断言函数被调用expected
和actual
和
我们指定参数很重要。但是,在 Rust 中,它们被称为left
和right
,以及我们指定预期值和值的顺序
代码生成无关紧要。我们可以将此测试中的断言写为assert_eq!(4, result)
,这将产生相同的失败消息
显示assertion failed: `(left == right)`
.
这assert_ne!
如果我们给它的两个值不相等,则 macro 将传递,
如果它们相等,则失败。这个宏在我们不确定的情况下最有用
值会是什么,但我们知道值绝对不应该是什么。
例如,如果我们正在测试一个保证会更改其输入的函数
在某种程度上,但 input 的更改方式取决于
在我们运行测试的那一周,最好断言的可能是
函数的 output 不等于 input。
在表面之下,assert_eq!
和assert_ne!
宏使用运算符 和==
!=
分别。当断言失败时,这些宏会打印其
使用调试格式的参数,这意味着要比较的值必须
实现PartialEq
和Debug
性状。所有基元类型和大多数
标准库类型实现这些特征。对于结构体和枚举
你定义自己,你需要实施PartialEq
要断言
那些类型。您还需要实施Debug
以打印
断言失败。因为这两个 trait 都是可派生的 trait,如 中所述
示例 5-12 中,这通常与添加#[derive(PartialEq, Debug)]
注解添加到您的结构或枚举定义中。看
附录 C,“可衍生性状”,了解更多信息
有关这些特征和其他可派生特征的详细信息。
添加自定义失败消息
您还可以添加自定义消息,以将失败消息打印为
可选参数添加到assert!
,assert_eq!
和assert_ne!
宏。任何
在将所需参数传递给format!
宏(在第 8 章的“与 Operator 或+
format!
宏”部分),因此您可以传递包含占位符和
值以放入这些占位符中。自定义消息可用于记录
断言的含义;当测试失败时,您将更好地了解
问题出在代码上。{}
例如,假设我们有一个按名字问候人们的函数,我们 想要测试我们传递给函数的名称是否出现在输出中:
文件名: src/lib.rs
pub fn greeting(name: &str) -> String {
format!("Hello {name}!")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn greeting_contains_name() {
let result = greeting("Carol");
assert!(result.contains("Carol"));
}
}
此计划的要求尚未达成一致,我们正在
很确定Hello
问候语开头的文本将发生变化。我们
决定我们不想在需求发生变化时更新测试,
因此,而不是检查是否与从greeting
函数,我们只需断言输出包含
input 参数。
现在让我们通过更改greeting
排除name
要查看默认测试失败的情况:
pub fn greeting(name: &str) -> String {
String::from("Hello!")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn greeting_contains_name() {
let result = greeting("Carol");
assert!(result.contains("Carol"));
}
}
运行此测试将生成以下内容:
$ cargo test
Compiling greeter v0.1.0 (file:///projects/greeter)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.91s
Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a)
running 1 test
test tests::greeting_contains_name ... FAILED
failures:
---- tests::greeting_contains_name stdout ----
thread 'tests::greeting_contains_name' panicked at src/lib.rs:12:9:
assertion failed: result.contains("Carol")
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::greeting_contains_name
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
此结果仅指示断言失败,以及
断言已打开。更有用的失败消息将打印greeting
功能。让我们添加一条由格式
string 中,其中的占位符填充了我们从greeting
功能:
pub fn greeting(name: &str) -> String {
String::from("Hello!")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn greeting_contains_name() {
let result = greeting("Carol");
assert!(
result.contains("Carol"),
"Greeting did not contain name, value was `{result}`"
);
}
}
现在,当我们运行测试时,我们将收到一条信息量更大的错误消息:
$ cargo test
Compiling greeter v0.1.0 (file:///projects/greeter)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.93s
Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a)
running 1 test
test tests::greeting_contains_name ... FAILED
failures:
---- tests::greeting_contains_name stdout ----
thread 'tests::greeting_contains_name' panicked at src/lib.rs:12:9:
Greeting did not contain name, value was `Hello!`
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::greeting_contains_name
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
我们可以看到我们在测试输出中实际获得的值,这将对我们有所帮助 调试发生了什么,而不是我们预期发生的事情。
检查 Panicsshould_panic
除了检查返回值之外,检查我们的代码是否
按照我们的预期处理错误条件。例如,考虑Guess
类型
我们在第 9 章示例 9-13 中创建的。其他使用Guess
取决于Guess
instances 将仅包含值
介于 1 和 100 之间。我们可以编写一个测试,确保尝试创建一个Guess
值超出该范围的实例 panic。
我们通过添加属性should_panic
添加到我们的测试功能。这
如果函数内部的代码 panic,则测试通过;如果代码
内部的函数不会 panic。
示例 11-8 显示了一个测试,该测试检查Guess::new
在我们预期的时候发生。
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {value}.");
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn greater_than_100() {
Guess::new(200);
}
}
panic!
我们将#[should_panic]
属性#[test]
attribute 和
在它适用的 test 函数之前。让我们看看这个测试时的结果
通过:
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s
Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
running 1 test
test tests::greater_than_100 - should panic ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests guessing_game
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
看起来不错!现在让我们通过删除条件
该new
如果该值大于 100,函数将 panic:
pub struct Guess {
value: i32,
}
// --snip--
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!("Guess value must be between 1 and 100, got {value}.");
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn greater_than_100() {
Guess::new(200);
}
}
当我们运行示例 11-8 中的测试时,它会失败:
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.62s
Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
running 1 test
test tests::greater_than_100 - should panic ... FAILED
failures:
---- tests::greater_than_100 stdout ----
note: test did not panic as expected
failures:
tests::greater_than_100
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
在这种情况下,我们没有收到非常有用的消息,但是当我们查看测试
函数,我们看到它被注释为#[should_panic]
.我们得到的失败
表示 test 函数中的代码没有导致 panic。
使用should_panic
可能不精确。一个should_panic
测试将
通过,即使测试因与我们不同的原因而 panic
期待。要使should_panic
testing 更精确,我们可以添加一个可选的expected
参数添加到should_panic
属性。测试框架将
确保 failure 消息包含提供的文本。例如
考虑修改后的代码Guess
在示例 11-9 中,其中new
功能
panics 并显示不同的消息,具体取决于值是否太小或
太大了。
pub struct Guess {
value: i32,
}
// --snip--
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!(
"Guess value must be greater than or equal to 1, got {value}."
);
} else if value > 100 {
panic!(
"Guess value must be less than or equal to 100, got {value}."
);
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "less than or equal to 100")]
fn greater_than_100() {
Guess::new(200);
}
}
panic!
包含包含指定子字符串的 panic 消息此测试将通过,因为我们在should_panic
属性的expected
parameter 是消息的子字符串,该Guess::new
function panics with 一起。我们可以指定整个 panic 消息,即我们
expect 的 intent 函数,在本例中为Guess value must be less than or equal to 100, got 200
.您选择指定的内容取决于 panic 的程度
消息是唯一的或动态的,以及您希望测试的精确程度。在这个
case,则 panic 消息的子字符串足以确保
test 函数执行else if value > 100
箱。
要查看should_panic
test 替换为expected
消息
失败,让我们通过交换if value < 1
和else if value > 100
块:
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!(
"Guess value must be less than or equal to 100, got {value}."
);
} else if value > 100 {
panic!(
"Guess value must be greater than or equal to 1, got {value}."
);
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "less than or equal to 100")]
fn greater_than_100() {
Guess::new(200);
}
}
这一次,当我们运行should_panic
test,它将失败:
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
running 1 test
test tests::greater_than_100 - should panic ... FAILED
failures:
---- tests::greater_than_100 stdout ----
thread 'tests::greater_than_100' panicked at src/lib.rs:12:13:
Guess value must be greater than or equal to 1, got 200.
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: panic did not contain expected string
panic message: `"Guess value must be greater than or equal to 1, got 200."`,
expected substring: `"less than or equal to 100"`
failures:
tests::greater_than_100
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
失败消息表明此测试确实如我们预期的那样出现 panic。
但 panic 消息不包含预期的字符串less than or equal to 100
.在这种情况下,我们确实收到的恐慌消息是Guess value must be greater than or equal to 1, got 200.
现在我们可以开始弄清楚在哪里
我们的 bug 是!
用Result<T, E>
in 测试
到目前为止,我们的测试在失败时都会感到恐慌。我们还可以编写使用Result<T, E>
!这是示例 11-1 中的测试,重写为使用Result<T, E>
并返回一个Err
而不是惊慌:
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() -> Result<(), String> {
let result = add(2, 2);
if result == 4 {
Ok(())
} else {
Err(String::from("two plus two does not equal four"))
}
}
}
这it_works
函数现在具有Result<(), String>
return 类型。在
body 的 body 函数,而不是调用assert_eq!
宏,我们返回Ok(())
当测试通过并且Err
替换为String
里面当测试
失败。
编写测试,使其返回Result<T, E>
使您能够使用问题
mark 运算符,这是一种方便的编写方式
测试中,如果其中的任何作返回Err
变体。
您不能使用#[should_panic]
对使用Result<T, E>
.要断言作返回Err
变体,请不要使用
问号运算符Result<T, E>
价值。相反,请使用assert!(value.is_err())
.
现在,您已经了解了编写测试的几种方法,让我们看看发生了什么
当我们运行测试并探索我们可以使用的不同选项cargo test
.
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准