diff --git a/docs/ch03-05-control-flow.html b/docs/ch03-05-control-flow.html index a9c724f..5ae5344 100644 --- a/docs/ch03-05-control-flow.html +++ b/docs/ch03-05-control-flow.html @@ -284,8 +284,7 @@ the value is: 50
所有数组中的五个元素都如期被打印出来。尽管index
在某一时刻会到达值5
,不过循环在其尝试从数组获取第六个值(会越界)之前就停止了。
不过这个过程是容易出错的;如果索引长度不正确会导致程序 panic。这也使程序更慢,因为编译器增加了运行时代码来对每次循环的每个元素进行条件检查。
可以使用for
循环来对一个集合的每个元素执行一些代码,来作为一个更有效率替代。for
循环看起来像这样:
当运行这段代码,将看到与列表 3-5 一样的输出。更为重要的是,我们增强了代码安全性并消除了出现可能会导致超出数组的结尾或遍历长度不够而缺少一些元素这类 bug 机会。
例如,在列表 3-5 的代码中,如果从数组a
中移除一个元素但忘记更新条件为while index < 4
,代码将会 panic。使用for
循环的话,就不需要惦记着在更新数组元素数量时修改其他的代码了。
for
循环的安全性和简洁性使得它在成为 Rust 中使用最多的循环结构。即使是在想要循环执行代码特定次数时,例如列表 3-5 中使用while
循环的倒计时例子,大部分 Rustacean 也会使用for
循环。这么做的方式是使用Range
,它是标准库提供的用来生成从一个数字开始到另一个数字结束的所有数字序列的类型。
注意这个模块文件中我们也使用了一个mod
声明;这是因为我们希望server
成为network
的一个子模块。
现在再次运行cargo build
。成功!不过我们还需要再提取出另一个模块:server
。因为这是一个子模块————也就是模块中的模块————目前的将模块提取到对应名字的文件中的策略就不管用了。如果我们仍这么尝试则会出现错误。对 src/network.rs 的第一个修改是用mod server;
替换server
模块的内容:
现在再次运行cargo build
。成功!不过我们还需要再提取出另一个模块:server
。因为这是一个子模块——也就是模块中的模块——目前的将模块提取到对应名字的文件中的策略就不管用了。如果我们仍这么尝试则会出现错误。对 src/network.rs 的第一个修改是用mod server;
替换server
模块的内容:
Filename: src/network.rs
fn connect() {
}
diff --git a/docs/ch11-01-writing-tests.html b/docs/ch11-01-writing-tests.html
index de8b0dc..11785ec 100644
--- a/docs/ch11-01-writing-tests.html
+++ b/docs/ch11-01-writing-tests.html
@@ -264,133 +264,285 @@ mod tests {
}
}
-Cargo 编译并运行了测试。这里有两部分输出:本章我们将关注第一部分。第二部分是文档测试的输出,第十四章会介绍他们。现在注意看这一行:
-test it_works ... ok
+因为这里can_hold
函数的正确结果是false
,我们需要将这个结果取反后传递给assert!
宏。这样的话,测试就会通过而can_hold
将返回false
:
+running 2 tests
+test tests::smaller_can_hold_larger ... ok
+test tests::larger_can_hold_smaller ... ok
+
+test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured
-it_works
文本来源于测试函数的名称。
-这里也有一行总结告诉我们所有测试的聚合结果:
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+这个通过的测试!现在让我们看看如果引入一个 bug 的话测试结果会发生什么。将can_hold
方法中比较长度时本应使用大于号的地方改成小于号:
+#[derive(Debug)]
+pub struct Rectangle {
+ length: u32,
+ width: u32,
+}
+
+impl Rectangle {
+ pub fn can_hold(&self, other: &Rectangle) -> bool {
+ self.length < other.length && self.width > other.width
+ }
+}
-assert!
宏
-空的测试函数之所以能通过是因为任何没有panic!
的测试都是通过的,而任何panic!
的测试都算是失败。让我们使用`assert!宏来使测试失败:
+现在运行测试会产生:
+running 2 tests
+test tests::smaller_can_hold_larger ... ok
+test tests::larger_can_hold_smaller ... FAILED
+
+failures:
+
+---- tests::larger_can_hold_smaller stdout ----
+ thread 'tests::larger_can_hold_smaller' panicked at 'assertion failed:
+ larger.can_hold(&smaller)', src/lib.rs:22
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+
+failures:
+ tests::larger_can_hold_smaller
+
+test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured
+
+我们的测试捕获了 bug!因为larger.length
是 8 而smaller.length
是 5,can_hold
中的长度比较现在返回false
因为 8 不小于 5。
+使用assert_eq!
和assert_ne!
宏来测试相等
+测试功能的一个常用方法是将需要测试代码的值与期望值做比较,并检查是否相等。可以通过向assert!
宏传递一个使用==
宏的表达式来做到。不过这个操作实在是太常见了,以至于标注库提供了一对宏来方便处理这些操作:assert_eq!
和assert_ne!
。这两个宏分别比较两个值是相等还是不相等。当断言失败时他们也会打印出这两个值具体是什么,以便于观察测试为什么失败,而assert!
只会打印出它从==
表达式中得到了false
值,而不是导致false
值的原因。
+列表 11-7 中,让我们编写一个对其参数加二并返回结果的函数add_two
。接着使用assert_eq!
宏测试这个函数:
Filename: src/lib.rs
-#[test]
-fn it_works() {
- assert!(false);
+pub fn add_two(a: i32) -> i32 {
+ a + 2
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_adds_two() {
+ assert_eq!(4, add_two(2));
+ }
}
-assert!
宏由标准库提供,它获取一个参数,如果参数是true
,什么也不会发生。如果参数是false
,这个宏会panic!
。再次运行测试:
-$ cargo test
- Compiling adder v0.1.0 (file:///projects/adder)
- Finished debug [unoptimized + debuginfo] target(s) in 0.22 secs
- Running target/debug/deps/adder-abcabcabc
+
+测试通过了!
+running 1 test
+test tests::it_adds_two ... ok
-running 1 test
-test it_works ... FAILED
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+
+传递给assert_eq!
宏的第一个参数,4,等于调用add_two(2)
的结果。我们将会看到这个测试的那一行说test tests::it_adds_two ... ok
,ok
表明测试通过了!
+在代码中引入一个 bug 来看看使用assert_eq!
的测试失败是什么样的。修改add_two
函数的实现使其加 3:
+pub fn add_two(a: i32) -> i32 {
+ a + 3
+}
+
+再次运行测试:
+running 1 test
+test tests::it_adds_two ... FAILED
failures:
----- it_works stdout ----
- thread 'it_works' panicked at 'assertion failed: false', src/lib.rs:5
+---- tests::it_adds_two stdout ----
+ thread 'tests::it_adds_two' panicked at 'assertion failed: `(left ==
+ right)` (left: `4`, right: `5`)', src/lib.rs:11
note: Run with `RUST_BACKTRACE=1` for a backtrace.
-
failures:
- it_works
+ tests::it_adds_two
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
+
+测试捕获到了 bug!it_adds_two
测试失败并显示信息assertion failed: `(left == right)` (left: `4`, right: `5`)
。这个信息有助于我们开始调试:它说assert_eq!
的left
参数是 4,而right
参数,也就是add_two(2)
的结果,是 5。
+注意在一些语言和测试框架中,断言两个值相等的函数的参数叫做expected
和actual
,而且指定参数的顺序是需要注意的。然而在 Rust 中,他们则叫做left
和right
,同时指定期望的值和被测试代码产生的值的顺序并不重要。这个测试中的断言也可以写成assert_eq!(add_two(2), 4)
,这时错误信息会变成assertion failed: `(left == right)` (left: `5`, right: `4`)
。
+assert_ne!
宏在传递给它的两个值不相等时通过而在相等时失败。这个宏在代码按照我们期望运行时不确定值应该是什么,不过知道他们绝对不应该是什么的时候最有用处。例如,如果一个函数确定会以某种方式改变其输出,不过这种方式由运行测试是星期几来决定,这时最好的断言可能就是函数的输出不等于其输入。
+assert_eq!
和assert_ne!
宏在底层分别使用了==
和!=
。当断言失败时,这些宏会使用调试格式打印出其参数,这意味着被比较的值必需实现了PartialEq
和Debug
trait。所有的基本类型和大部分标准库类型都实现了这些 trait。对于自定义的结构体和枚举,需要实现 PartialEq
才能断言他们的值是否相等。需要实现 Debug
才能在断言失败时打印他们的值。因为这两个 trait 都是可推导 trait,如第五章所提到的,通常可以直接在结构体或枚举上添加#[derive(PartialEq, Debug)]
注解。附录 C 中有更多关于这些和其他可推导 trait 的详细信息。
+自定义错误信息
+也可以向assert!
、assert_eq!
和assert_ne!
宏传递一个可选的参数来增加用于打印的自定义错误信息。任何在assert!
必需的一个参数和assert_eq!
和assert_ne!
必需的两个参数之后指定的参数都会传递给第八章讲到的format!
宏,所以可以传递一个包含{}
占位符的格式字符串和放入占位符的值。自定义信息有助于记录断言的意义,这样到测试失败时,就能更好的例子代码出了什么问题。
+例如,比如说有一个根据人名进行问候的函数,而我们希望测试将传递给函数的人名显示在输出中:
+Filename: src/lib.rs
+pub fn greeting(name: &str) -> String {
+ format!("Hello {}!", name)
+}
-error: test failed
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn greeting_contains_name() {
+ let result = greeting("Carol");
+ assert!(result.contains("Carol"));
+ }
+}
-Rust 表明测试失败了:
-test it_works ... FAILED
+这个程序的需求还没有被确定,而我们非常确定问候开始的Hello
文本不会改变。我们决定并不想在人名改变时
+不得不更新测试,所以相比检查greeting
函数返回的确切的值,我们将仅仅断言输出的文本中包含输入参数。
+让我们通过改变greeting
不包含name
来在代码中引入一个 bug 来测试失败时是怎样的,
+pub fn greeting(name: &str) -> String {
+ String::from("Hello!")
+}
-并展示了测试是因为src/lib.rs的第 5 行
assert!宏得到了一个
false`值而失败的:
-thread 'it_works' panicked at 'assertion failed: false', src/lib.rs:5
+运行测试会产生:
+running 1 test
+test tests::greeting_contains_name ... FAILED
+
+failures:
+
+---- tests::greeting_contains_name stdout ----
+ thread 'tests::greeting_contains_name' panicked at 'assertion failed:
+ result.contains("Carol")', src/lib.rs:12
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+
+failures:
+ tests::greeting_contains_name
-失败的测试也体现在了总结行中:
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
+这仅仅告诉了我们断言失败了和失败的行号。一个更有用的错误信息应该打印出从greeting
函数得到的值。让我们改变测试函数来使用一个由包含占位符的格式字符串和从greeting
函数取得的值组成的自定义错误信息:
+#[test]
+fn greeting_contains_name() {
+ let result = greeting("Carol");
+ assert!(
+ result.contains("Carol"),
+ "Greeting did not contain name, value was `{}`", result
+ );
+}
-使用assert_eq!
和assert_ne!
宏来测试相等
-测试功能的一个常用方法是将需要测试代码的值与期望值做比较,并检查是否相等。可以通过向assert!
宏传递一个使用==
宏的表达式来做到。不过这个操作实在是太常见了,以至于标注库提供了一对宏来编译处理这些操作:assert_eq!
和assert_ne!
。这两个宏分别比较两个值是相等还是不相等。使用这些宏的另一个优势是当断言失败时他们会打印出这两个值具体是什么,以便于观察测试为什么失败,而assert!
只会打印出它从==
表达式中得到了false
值。
-下面是分别使用这两个宏其会测试通过的例子:
+现在如果再次运行测试,将会看到更有价值的错误信息:
+---- tests::greeting_contains_name stdout ----
+ thread 'tests::greeting_contains_name' panicked at 'Result did not contain
+ name, value was `Hello`', src/lib.rs:12
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+
+可以在测试输出中看到所取得的确切的值,这会帮助我们理解发生了什么而不是期望发生什么。
+使用should_panic
检查 panic
+除了检查代码是否返回期望的正确的值之外,检查代码是否按照期望处理错误情况也是很重要的。例如,考虑第九章列表 9-8 创建的Guess
类型。其他使用Guess
的代码依赖于Guess
实例只会包含 1 到 100 的值的保证。可以编写一个测试来确保创建一个超出范围的值的Guess
实例会 panic。
+可以通过对函数增加另一个属性should_panic
来实现这些。这个属性在函数中的代码 panic 时会通过,而在其中的代码没有 panic 时失败。
+列表 11-8 展示了如何编写一个测试来检查Guess::new
按照我们的期望出现的错误情况:
Filename: src/lib.rs
-#[test]
-fn it_works() {
- assert_eq!("Hello", "Hello");
-
- assert_ne!("Hello", "world");
+struct Guess {
+ value: u32,
}
-
-也可以对这些宏指定可选的第三个参数,它是一个会加入错误信息的自定义文本。这两个宏展开后的逻辑看起来像这样:
-// assert_eq! - panic if the values aren't equal
-if left_val != right_val {
- panic!(
- "assertion failed: `(left == right)` (left: `{:?}`, right: `{:?}`): {}"
- left_val,
- right_val,
- optional_custom_message
- )
+
+impl Guess {
+ pub fn new(value: u32) -> Guess {
+ if value < 1 || value > 100 {
+ panic!("Guess value must be between 1 and 100, got {}.", value);
+ }
+
+ Guess {
+ value: value,
+ }
+ }
}
-// assert_ne! - panic if the values are equal
-if left_val == right_val {
- panic!(
- "assertion failed: `(left != right)` (left: `{:?}`, right: `{:?}`): {}"
- left_val,
- right_val,
- optional_custom_message
- )
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ #[should_panic]
+ fn greater_than_100() {
+ Guess::new(200);
+ }
}
-看看这个因为hello
不等于world
而失败的测试。我们还增加了一个自定义的错误信息,greeting operation failed
:
-Filename: src/lib.rs
-#[test]
-fn a_simple_case() {
- let result = "hello"; // this value would come from running your code
- assert_eq!(result, "world", "greeting operation failed");
+
+#[should_panic]
属性位于#[test]
之后和对应的测试函数之前。让我们看看测试通过时它时什么样子:
+running 1 test
+test tests::greater_than_100 ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+
+看起来不错!现在在代码中引入 bug,通过移除new
函数在值大于 100 时会 panic 的条件:
+# struct Guess {
+# value: u32,
+# }
+#
+impl Guess {
+ pub fn new(value: u32) -> Guess {
+ if value < 1 {
+ panic!("Guess value must be between 1 and 100, got {}.", value);
+ }
+
+ Guess {
+ value: value,
+ }
+ }
}
-毫无疑问运行这个测试会失败,而错误信息解释了为什么测试失败了并且带有我们的指定的自定义错误信息:
----- a_simple_case stdout ----
- thread 'a_simple_case' panicked at 'assertion failed: `(left == right)`
- (left: `"hello"`, right: `"world"`): greeting operation failed',
- src/main.rs:4
+如果运行列表 11-8 的测试,它会失败:
+running 1 test
+test tests::greater_than_100 ... FAILED
+
+failures:
+
+failures:
+ tests::greater_than_100
+
+test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
-assert_eq!
的两个参数被称为 "left" 和 "right" ,而不是 "expected" 和 "actual" ;值的顺序和硬编码的值并没有什么影响。
-因为这些宏使用了==
和!=
运算符并使用调试格式打印这些值,进行比较的值必须实现PartialEq
和Debug
trait。Rust 提供的类型实现了这些 trait,不过自定义的结构体和枚举则需要自己实现PartialEq
以便能够断言这些值是否相等,和实现Debug
以便在断言失败时打印出这些值。因为第五章提到过这两个 trait 都是 derivable trait,所以通常可以直接在结构体或枚举上加上#[derive(PartialEq, Debug)]
注解。查看附录 C 来寻找更多关于这些和其他 derivable trait 的信息。
-使用should_panic
测试期望的失败
-可以使用另一个属性来反转测试中的失败:should_panic
。这在测试调用特定的函数会产生错误的函数时很有帮助。例如,让我们测试第八章中的一些我们知道会 panic 的代码:尝试使用 range 语法和并不组成完整字母的字节索引来创建一个字符串 slice。在有#[test]
属性的函数之前增加#[should_panic]
属性,如列表 11-1 所示:
-
-这个测试是成功的,因为我们表示代码应该会 panic。相反如果代码因为某种原因没有产生panic!
则测试会失败。
-使用should_panic
的测试是脆弱的,因为难以保证测试不会因为一个不同于我们期望的原因失败。为了帮助解决这个问题,should_panic
属性可以增加一个可选的expected
参数。测试工具会确保错误信息里包含我们提供的文本。一个比列表 11-1 更健壮的版本如列表 11-2 所示:
-
-请自行尝试当should_panic
的测试出现 panic 但并不符合期望的信息时会发生什么:在测试中因为不同原因造成panic!
,或者将期望的 panic 信息改为并不与字母字节边界 panic 信息相匹配。
+这一次运行should_panic
测试,它会失败:
+running 1 test
+test tests::greater_than_100 ... FAILED
+
+failures:
+
+---- tests::greater_than_100 stdout ----
+ thread 'tests::greater_than_100' panicked at 'Guess value must be greater
+ than or equal to 1, got 200.', src/lib.rs:10
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+note: Panic did not include expected string 'Guess value must be less than or
+equal to 100'
+
+failures:
+ tests::greater_than_100
+
+test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
+
+错误信息表明测试确实如期望 panic 了,不过 panic 信息did not include expected string 'Guess value must be less than or equal to 100'
。可以看到我们的到的 panic 信息,在这个例子中是Guess value must be greater than or equal to 1, got 200.
。这样就可以开始寻找 bug 在哪了!
+现在我们讲完了编写测试的方法,让我们看看运行测试时会发生什么并讨论可以用于cargo test
的不同选项。
diff --git a/docs/print.html b/docs/print.html
index 967a5f7..4e99b8d 100644
--- a/docs/print.html
+++ b/docs/print.html
@@ -1545,8 +1545,7 @@ the value is: 50
所有数组中的五个元素都如期被打印出来。尽管index
在某一时刻会到达值5
,不过循环在其尝试从数组获取第六个值(会越界)之前就停止了。
不过这个过程是容易出错的;如果索引长度不正确会导致程序 panic。这也使程序更慢,因为编译器增加了运行时代码来对每次循环的每个元素进行条件检查。
可以使用for
循环来对一个集合的每个元素执行一些代码,来作为一个更有效率替代。for
循环看起来像这样:
-
+
当运行这段代码,将看到与列表 3-5 一样的输出。更为重要的是,我们增强了代码安全性并消除了出现可能会导致超出数组的结尾或遍历长度不够而缺少一些元素这类 bug 机会。
例如,在列表 3-5 的代码中,如果从数组a
中移除一个元素但忘记更新条件为while index < 4
,代码将会 panic。使用for
循环的话,就不需要惦记着在更新数组元素数量时修改其他的代码了。
for
循环的安全性和简洁性使得它在成为 Rust 中使用最多的循环结构。即使是在想要循环执行代码特定次数时,例如列表 3-5 中使用while
循环的倒计时例子,大部分 Rustacean 也会使用for
循环。这么做的方式是使用Range
,它是标准库提供的用来生成从一个数字开始到另一个数字结束的所有数字序列的类型。
@@ -3179,7 +3176,7 @@ mod server {
}
注意这个模块文件中我们也使用了一个mod
声明;这是因为我们希望server
成为network
的一个子模块。
-现在再次运行cargo build
。成功!不过我们还需要再提取出另一个模块:server
。因为这是一个子模块————也就是模块中的模块————目前的将模块提取到对应名字的文件中的策略就不管用了。如果我们仍这么尝试则会出现错误。对 src/network.rs 的第一个修改是用mod server;
替换server
模块的内容:
+现在再次运行cargo build
。成功!不过我们还需要再提取出另一个模块:server
。因为这是一个子模块——也就是模块中的模块——目前的将模块提取到对应名字的文件中的策略就不管用了。如果我们仍这么尝试则会出现错误。对 src/network.rs 的第一个修改是用mod server;
替换server
模块的内容:
Filename: src/network.rs
fn connect() {
}
@@ -5582,133 +5579,285 @@ mod tests {
}
}
-Cargo 编译并运行了测试。这里有两部分输出:本章我们将关注第一部分。第二部分是文档测试的输出,第十四章会介绍他们。现在注意看这一行:
-test it_works ... ok
+因为这里can_hold
函数的正确结果是false
,我们需要将这个结果取反后传递给assert!
宏。这样的话,测试就会通过而can_hold
将返回false
:
+running 2 tests
+test tests::smaller_can_hold_larger ... ok
+test tests::larger_can_hold_smaller ... ok
+
+test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured
-it_works
文本来源于测试函数的名称。
-这里也有一行总结告诉我们所有测试的聚合结果:
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+这个通过的测试!现在让我们看看如果引入一个 bug 的话测试结果会发生什么。将can_hold
方法中比较长度时本应使用大于号的地方改成小于号:
+#[derive(Debug)]
+pub struct Rectangle {
+ length: u32,
+ width: u32,
+}
+
+impl Rectangle {
+ pub fn can_hold(&self, other: &Rectangle) -> bool {
+ self.length < other.length && self.width > other.width
+ }
+}
+
+现在运行测试会产生:
+running 2 tests
+test tests::smaller_can_hold_larger ... ok
+test tests::larger_can_hold_smaller ... FAILED
+
+failures:
+
+---- tests::larger_can_hold_smaller stdout ----
+ thread 'tests::larger_can_hold_smaller' panicked at 'assertion failed:
+ larger.can_hold(&smaller)', src/lib.rs:22
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+
+failures:
+ tests::larger_can_hold_smaller
+
+test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured
-assert!
宏
-空的测试函数之所以能通过是因为任何没有panic!
的测试都是通过的,而任何panic!
的测试都算是失败。让我们使用`assert!宏来使测试失败:
+我们的测试捕获了 bug!因为larger.length
是 8 而smaller.length
是 5,can_hold
中的长度比较现在返回false
因为 8 不小于 5。
+使用assert_eq!
和assert_ne!
宏来测试相等
+测试功能的一个常用方法是将需要测试代码的值与期望值做比较,并检查是否相等。可以通过向assert!
宏传递一个使用==
宏的表达式来做到。不过这个操作实在是太常见了,以至于标注库提供了一对宏来方便处理这些操作:assert_eq!
和assert_ne!
。这两个宏分别比较两个值是相等还是不相等。当断言失败时他们也会打印出这两个值具体是什么,以便于观察测试为什么失败,而assert!
只会打印出它从==
表达式中得到了false
值,而不是导致false
值的原因。
+列表 11-7 中,让我们编写一个对其参数加二并返回结果的函数add_two
。接着使用assert_eq!
宏测试这个函数:
Filename: src/lib.rs
-#[test]
-fn it_works() {
- assert!(false);
+pub fn add_two(a: i32) -> i32 {
+ a + 2
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_adds_two() {
+ assert_eq!(4, add_two(2));
+ }
}
-assert!
宏由标准库提供,它获取一个参数,如果参数是true
,什么也不会发生。如果参数是false
,这个宏会panic!
。再次运行测试:
-$ cargo test
- Compiling adder v0.1.0 (file:///projects/adder)
- Finished debug [unoptimized + debuginfo] target(s) in 0.22 secs
- Running target/debug/deps/adder-abcabcabc
+
+测试通过了!
+running 1 test
+test tests::it_adds_two ... ok
-running 1 test
-test it_works ... FAILED
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+
+传递给assert_eq!
宏的第一个参数,4,等于调用add_two(2)
的结果。我们将会看到这个测试的那一行说test tests::it_adds_two ... ok
,ok
表明测试通过了!
+在代码中引入一个 bug 来看看使用assert_eq!
的测试失败是什么样的。修改add_two
函数的实现使其加 3:
+pub fn add_two(a: i32) -> i32 {
+ a + 3
+}
+
+再次运行测试:
+running 1 test
+test tests::it_adds_two ... FAILED
failures:
----- it_works stdout ----
- thread 'it_works' panicked at 'assertion failed: false', src/lib.rs:5
+---- tests::it_adds_two stdout ----
+ thread 'tests::it_adds_two' panicked at 'assertion failed: `(left ==
+ right)` (left: `4`, right: `5`)', src/lib.rs:11
note: Run with `RUST_BACKTRACE=1` for a backtrace.
-
failures:
- it_works
+ tests::it_adds_two
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
+
+测试捕获到了 bug!it_adds_two
测试失败并显示信息assertion failed: `(left == right)` (left: `4`, right: `5`)
。这个信息有助于我们开始调试:它说assert_eq!
的left
参数是 4,而right
参数,也就是add_two(2)
的结果,是 5。
+注意在一些语言和测试框架中,断言两个值相等的函数的参数叫做expected
和actual
,而且指定参数的顺序是需要注意的。然而在 Rust 中,他们则叫做left
和right
,同时指定期望的值和被测试代码产生的值的顺序并不重要。这个测试中的断言也可以写成assert_eq!(add_two(2), 4)
,这时错误信息会变成assertion failed: `(left == right)` (left: `5`, right: `4`)
。
+assert_ne!
宏在传递给它的两个值不相等时通过而在相等时失败。这个宏在代码按照我们期望运行时不确定值应该是什么,不过知道他们绝对不应该是什么的时候最有用处。例如,如果一个函数确定会以某种方式改变其输出,不过这种方式由运行测试是星期几来决定,这时最好的断言可能就是函数的输出不等于其输入。
+assert_eq!
和assert_ne!
宏在底层分别使用了==
和!=
。当断言失败时,这些宏会使用调试格式打印出其参数,这意味着被比较的值必需实现了PartialEq
和Debug
trait。所有的基本类型和大部分标准库类型都实现了这些 trait。对于自定义的结构体和枚举,需要实现 PartialEq
才能断言他们的值是否相等。需要实现 Debug
才能在断言失败时打印他们的值。因为这两个 trait 都是可推导 trait,如第五章所提到的,通常可以直接在结构体或枚举上添加#[derive(PartialEq, Debug)]
注解。附录 C 中有更多关于这些和其他可推导 trait 的详细信息。
+自定义错误信息
+也可以向assert!
、assert_eq!
和assert_ne!
宏传递一个可选的参数来增加用于打印的自定义错误信息。任何在assert!
必需的一个参数和assert_eq!
和assert_ne!
必需的两个参数之后指定的参数都会传递给第八章讲到的format!
宏,所以可以传递一个包含{}
占位符的格式字符串和放入占位符的值。自定义信息有助于记录断言的意义,这样到测试失败时,就能更好的例子代码出了什么问题。
+例如,比如说有一个根据人名进行问候的函数,而我们希望测试将传递给函数的人名显示在输出中:
+Filename: src/lib.rs
+pub fn greeting(name: &str) -> String {
+ format!("Hello {}!", name)
+}
-error: test failed
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn greeting_contains_name() {
+ let result = greeting("Carol");
+ assert!(result.contains("Carol"));
+ }
+}
-Rust 表明测试失败了:
-test it_works ... FAILED
+这个程序的需求还没有被确定,而我们非常确定问候开始的Hello
文本不会改变。我们决定并不想在人名改变时
+不得不更新测试,所以相比检查greeting
函数返回的确切的值,我们将仅仅断言输出的文本中包含输入参数。
+让我们通过改变greeting
不包含name
来在代码中引入一个 bug 来测试失败时是怎样的,
+pub fn greeting(name: &str) -> String {
+ String::from("Hello!")
+}
-并展示了测试是因为src/lib.rs的第 5 行
assert!宏得到了一个
false`值而失败的:
-thread 'it_works' panicked at 'assertion failed: false', src/lib.rs:5
+运行测试会产生:
+running 1 test
+test tests::greeting_contains_name ... FAILED
+
+failures:
+
+---- tests::greeting_contains_name stdout ----
+ thread 'tests::greeting_contains_name' panicked at 'assertion failed:
+ result.contains("Carol")', src/lib.rs:12
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+
+failures:
+ tests::greeting_contains_name
+
+这仅仅告诉了我们断言失败了和失败的行号。一个更有用的错误信息应该打印出从greeting
函数得到的值。让我们改变测试函数来使用一个由包含占位符的格式字符串和从greeting
函数取得的值组成的自定义错误信息:
+#[test]
+fn greeting_contains_name() {
+ let result = greeting("Carol");
+ assert!(
+ result.contains("Carol"),
+ "Greeting did not contain name, value was `{}`", result
+ );
+}
-失败的测试也体现在了总结行中:
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
+现在如果再次运行测试,将会看到更有价值的错误信息:
+---- tests::greeting_contains_name stdout ----
+ thread 'tests::greeting_contains_name' panicked at 'Result did not contain
+ name, value was `Hello`', src/lib.rs:12
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
-使用assert_eq!
和assert_ne!
宏来测试相等
-测试功能的一个常用方法是将需要测试代码的值与期望值做比较,并检查是否相等。可以通过向assert!
宏传递一个使用==
宏的表达式来做到。不过这个操作实在是太常见了,以至于标注库提供了一对宏来编译处理这些操作:assert_eq!
和assert_ne!
。这两个宏分别比较两个值是相等还是不相等。使用这些宏的另一个优势是当断言失败时他们会打印出这两个值具体是什么,以便于观察测试为什么失败,而assert!
只会打印出它从==
表达式中得到了false
值。
-下面是分别使用这两个宏其会测试通过的例子:
+可以在测试输出中看到所取得的确切的值,这会帮助我们理解发生了什么而不是期望发生什么。
+使用should_panic
检查 panic
+除了检查代码是否返回期望的正确的值之外,检查代码是否按照期望处理错误情况也是很重要的。例如,考虑第九章列表 9-8 创建的Guess
类型。其他使用Guess
的代码依赖于Guess
实例只会包含 1 到 100 的值的保证。可以编写一个测试来确保创建一个超出范围的值的Guess
实例会 panic。
+可以通过对函数增加另一个属性should_panic
来实现这些。这个属性在函数中的代码 panic 时会通过,而在其中的代码没有 panic 时失败。
+列表 11-8 展示了如何编写一个测试来检查Guess::new
按照我们的期望出现的错误情况:
Filename: src/lib.rs
-#[test]
-fn it_works() {
- assert_eq!("Hello", "Hello");
-
- assert_ne!("Hello", "world");
+struct Guess {
+ value: u32,
}
-
-也可以对这些宏指定可选的第三个参数,它是一个会加入错误信息的自定义文本。这两个宏展开后的逻辑看起来像这样:
-// assert_eq! - panic if the values aren't equal
-if left_val != right_val {
- panic!(
- "assertion failed: `(left == right)` (left: `{:?}`, right: `{:?}`): {}"
- left_val,
- right_val,
- optional_custom_message
- )
+
+impl Guess {
+ pub fn new(value: u32) -> Guess {
+ if value < 1 || value > 100 {
+ panic!("Guess value must be between 1 and 100, got {}.", value);
+ }
+
+ Guess {
+ value: value,
+ }
+ }
}
-// assert_ne! - panic if the values are equal
-if left_val == right_val {
- panic!(
- "assertion failed: `(left != right)` (left: `{:?}`, right: `{:?}`): {}"
- left_val,
- right_val,
- optional_custom_message
- )
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ #[should_panic]
+ fn greater_than_100() {
+ Guess::new(200);
+ }
}
-看看这个因为hello
不等于world
而失败的测试。我们还增加了一个自定义的错误信息,greeting operation failed
:
-Filename: src/lib.rs
-#[test]
-fn a_simple_case() {
- let result = "hello"; // this value would come from running your code
- assert_eq!(result, "world", "greeting operation failed");
+
+#[should_panic]
属性位于#[test]
之后和对应的测试函数之前。让我们看看测试通过时它时什么样子:
+running 1 test
+test tests::greater_than_100 ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+
+看起来不错!现在在代码中引入 bug,通过移除new
函数在值大于 100 时会 panic 的条件:
+# struct Guess {
+# value: u32,
+# }
+#
+impl Guess {
+ pub fn new(value: u32) -> Guess {
+ if value < 1 {
+ panic!("Guess value must be between 1 and 100, got {}.", value);
+ }
+
+ Guess {
+ value: value,
+ }
+ }
}
-毫无疑问运行这个测试会失败,而错误信息解释了为什么测试失败了并且带有我们的指定的自定义错误信息:
----- a_simple_case stdout ----
- thread 'a_simple_case' panicked at 'assertion failed: `(left == right)`
- (left: `"hello"`, right: `"world"`): greeting operation failed',
- src/main.rs:4
+如果运行列表 11-8 的测试,它会失败:
+running 1 test
+test tests::greater_than_100 ... FAILED
+
+failures:
+
+failures:
+ tests::greater_than_100
+
+test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
-assert_eq!
的两个参数被称为 "left" 和 "right" ,而不是 "expected" 和 "actual" ;值的顺序和硬编码的值并没有什么影响。
-因为这些宏使用了==
和!=
运算符并使用调试格式打印这些值,进行比较的值必须实现PartialEq
和Debug
trait。Rust 提供的类型实现了这些 trait,不过自定义的结构体和枚举则需要自己实现PartialEq
以便能够断言这些值是否相等,和实现Debug
以便在断言失败时打印出这些值。因为第五章提到过这两个 trait 都是 derivable trait,所以通常可以直接在结构体或枚举上加上#[derive(PartialEq, Debug)]
注解。查看附录 C 来寻找更多关于这些和其他 derivable trait 的信息。
-使用should_panic
测试期望的失败
-可以使用另一个属性来反转测试中的失败:should_panic
。这在测试调用特定的函数会产生错误的函数时很有帮助。例如,让我们测试第八章中的一些我们知道会 panic 的代码:尝试使用 range 语法和并不组成完整字母的字节索引来创建一个字符串 slice。在有#[test]
属性的函数之前增加#[should_panic]
属性,如列表 11-1 所示:
-
-这个测试是成功的,因为我们表示代码应该会 panic。相反如果代码因为某种原因没有产生panic!
则测试会失败。
-使用should_panic
的测试是脆弱的,因为难以保证测试不会因为一个不同于我们期望的原因失败。为了帮助解决这个问题,should_panic
属性可以增加一个可选的expected
参数。测试工具会确保错误信息里包含我们提供的文本。一个比列表 11-1 更健壮的版本如列表 11-2 所示:
-
-请自行尝试当should_panic
的测试出现 panic 但并不符合期望的信息时会发生什么:在测试中因为不同原因造成panic!
,或者将期望的 panic 信息改为并不与字母字节边界 panic 信息相匹配。
+这一次运行should_panic
测试,它会失败:
+running 1 test
+test tests::greater_than_100 ... FAILED
+
+failures:
+
+---- tests::greater_than_100 stdout ----
+ thread 'tests::greater_than_100' panicked at 'Guess value must be greater
+ than or equal to 1, got 200.', src/lib.rs:10
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+note: Panic did not include expected string 'Guess value must be less than or
+equal to 100'
+
+failures:
+ tests::greater_than_100
+
+test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
+
+错误信息表明测试确实如期望 panic 了,不过 panic 信息did not include expected string 'Guess value must be less than or equal to 100'
。可以看到我们的到的 panic 信息,在这个例子中是Guess value must be greater than or equal to 1, got 200.
。这样就可以开始寻找 bug 在哪了!
+现在我们讲完了编写测试的方法,让我们看看运行测试时会发生什么并讨论可以用于cargo test
的不同选项。
运行测试
ch11-02-running-tests.md
diff --git a/src/ch03-05-control-flow.md b/src/ch03-05-control-flow.md
index 0c17184..6fb1f6d 100644
--- a/src/ch03-05-control-flow.md
+++ b/src/ch03-05-control-flow.md
@@ -305,7 +305,6 @@ the value is: 50
可以使用`for`循环来对一个集合的每个元素执行一些代码,来作为一个更有效率替代。`for`循环看起来像这样:
-
+
当运行这段代码,将看到与列表 3-5 一样的输出。更为重要的是,我们增强了代码安全性并消除了出现可能会导致超出数组的结尾或遍历长度不够而缺少一些元素这类 bug 机会。
diff --git a/src/ch07-01-mod-and-the-filesystem.md b/src/ch07-01-mod-and-the-filesystem.md
index 945188c..1b7a958 100644
--- a/src/ch07-01-mod-and-the-filesystem.md
+++ b/src/ch07-01-mod-and-the-filesystem.md
@@ -230,7 +230,7 @@ mod server {
注意这个模块文件中我们也使用了一个`mod`声明;这是因为我们希望`server`成为`network`的一个子模块。
-现在再次运行`cargo build`。成功!不过我们还需要再提取出另一个模块:`server`。因为这是一个子模块————也就是模块中的模块————目前的将模块提取到对应名字的文件中的策略就不管用了。如果我们仍这么尝试则会出现错误。对 *src/network.rs* 的第一个修改是用`mod server;`替换`server`模块的内容:
+现在再次运行`cargo build`。成功!不过我们还需要再提取出另一个模块:`server`。因为这是一个子模块——也就是模块中的模块——目前的将模块提取到对应名字的文件中的策略就不管用了。如果我们仍这么尝试则会出现错误。对 *src/network.rs* 的第一个修改是用`mod server;`替换`server`模块的内容:
Filename: src/network.rs
diff --git a/src/ch11-01-writing-tests.md b/src/ch11-01-writing-tests.md
index d7fe508..a03b4b7 100644
--- a/src/ch11-01-writing-tests.md
+++ b/src/ch11-01-writing-tests.md
@@ -169,7 +169,6 @@ test fails
`assert!`宏由标准库提供,在希望确保测试中一些条件为`true`时非常有用。需要向`assert!`宏提供一个计算为布尔值的参数。如果值是`true`,`assert!`什么也不做同时测试会通过。如果值为`false`,`assert!`调用`panic!`宏,这会导致测试失败。这是一个帮助我们检查代码是否以期望的方式运行的宏。
-
+这一次运行`should_panic`测试,它会失败:
+
+```
+running 1 test
+test tests::greater_than_100 ... FAILED
+
+failures:
-
+---- tests::greater_than_100 stdout ----
+ thread 'tests::greater_than_100' panicked at 'Guess value must be greater
+ than or equal to 1, got 200.', src/lib.rs:10
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+note: Panic did not include expected string 'Guess value must be less than or
+equal to 100'
-Listing 11-2: A test expecting a `panic!` with a particular message
+failures:
+ tests::greater_than_100
+
+test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
+```
-
-
+错误信息表明测试确实如期望 panic 了,不过 panic 信息`did not include expected string 'Guess value must be less than or equal to 100'`。可以看到我们的到的 panic 信息,在这个例子中是`Guess value must be greater than or equal to 1, got 200.`。这样就可以开始寻找 bug 在哪了!
-请自行尝试当`should_panic`的测试出现 panic 但并不符合期望的信息时会发生什么:在测试中因为不同原因造成`panic!`,或者将期望的 panic 信息改为并不与字母字节边界 panic 信息相匹配。
\ No newline at end of file
+现在我们讲完了编写测试的方法,让我们看看运行测试时会发生什么并讨论可以用于`cargo test`的不同选项。
\ No newline at end of file