diff --git a/docs/ch01-00-introduction.html b/docs/ch01-00-introduction.html index c3e3998..bb4f0ab 100644 --- a/docs/ch01-00-introduction.html +++ b/docs/ch01-00-introduction.html @@ -47,7 +47,7 @@
diff --git a/docs/ch01-01-installation.html b/docs/ch01-01-installation.html index 4719b8d..18ea843 100644 --- a/docs/ch01-01-installation.html +++ b/docs/ch01-01-installation.html @@ -47,7 +47,7 @@
diff --git a/docs/ch01-02-hello-world.html b/docs/ch01-02-hello-world.html index cc8531e..8a70dbe 100644 --- a/docs/ch01-02-hello-world.html +++ b/docs/ch01-02-hello-world.html @@ -47,7 +47,7 @@
diff --git a/docs/ch02-00-guessing-game-tutorial.html b/docs/ch02-00-guessing-game-tutorial.html index 31dbd43..751c0bd 100644 --- a/docs/ch02-00-guessing-game-tutorial.html +++ b/docs/ch02-00-guessing-game-tutorial.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-00-common-programming-concepts.html b/docs/ch03-00-common-programming-concepts.html index b58d95d..e682435 100644 --- a/docs/ch03-00-common-programming-concepts.html +++ b/docs/ch03-00-common-programming-concepts.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-01-variables-and-mutability.html b/docs/ch03-01-variables-and-mutability.html index 60c8ba2..a4d3db7 100644 --- a/docs/ch03-01-variables-and-mutability.html +++ b/docs/ch03-01-variables-and-mutability.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-02-data-types.html b/docs/ch03-02-data-types.html index cff220a..73e6cef 100644 --- a/docs/ch03-02-data-types.html +++ b/docs/ch03-02-data-types.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-03-how-functions-work.html b/docs/ch03-03-how-functions-work.html index e77ab8b..45d1d51 100644 --- a/docs/ch03-03-how-functions-work.html +++ b/docs/ch03-03-how-functions-work.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-04-comments.html b/docs/ch03-04-comments.html index 69b058c..70f776e 100644 --- a/docs/ch03-04-comments.html +++ b/docs/ch03-04-comments.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-05-control-flow.html b/docs/ch03-05-control-flow.html index 80bdec7..e675460 100644 --- a/docs/ch03-05-control-flow.html +++ b/docs/ch03-05-control-flow.html @@ -47,7 +47,7 @@
diff --git a/docs/ch04-00-understanding-ownership.html b/docs/ch04-00-understanding-ownership.html index 9c6ed97..acf8540 100644 --- a/docs/ch04-00-understanding-ownership.html +++ b/docs/ch04-00-understanding-ownership.html @@ -47,7 +47,7 @@
diff --git a/docs/ch04-01-what-is-ownership.html b/docs/ch04-01-what-is-ownership.html index 6689423..5010126 100644 --- a/docs/ch04-01-what-is-ownership.html +++ b/docs/ch04-01-what-is-ownership.html @@ -47,7 +47,7 @@
diff --git a/docs/ch04-02-references-and-borrowing.html b/docs/ch04-02-references-and-borrowing.html index 36fe73d..102f5b2 100644 --- a/docs/ch04-02-references-and-borrowing.html +++ b/docs/ch04-02-references-and-borrowing.html @@ -47,7 +47,7 @@
diff --git a/docs/ch04-03-slices.html b/docs/ch04-03-slices.html index e2e2146..5f14778 100644 --- a/docs/ch04-03-slices.html +++ b/docs/ch04-03-slices.html @@ -47,7 +47,7 @@
diff --git a/docs/ch05-00-structs.html b/docs/ch05-00-structs.html index d65981d..7509f9f 100644 --- a/docs/ch05-00-structs.html +++ b/docs/ch05-00-structs.html @@ -47,7 +47,7 @@
diff --git a/docs/ch05-01-method-syntax.html b/docs/ch05-01-method-syntax.html index 677b693..b079331 100644 --- a/docs/ch05-01-method-syntax.html +++ b/docs/ch05-01-method-syntax.html @@ -47,7 +47,7 @@
diff --git a/docs/ch06-00-enums.html b/docs/ch06-00-enums.html index f350a4b..b1218fa 100644 --- a/docs/ch06-00-enums.html +++ b/docs/ch06-00-enums.html @@ -47,7 +47,7 @@
diff --git a/docs/ch06-01-defining-an-enum.html b/docs/ch06-01-defining-an-enum.html index c4188d0..2185ba7 100644 --- a/docs/ch06-01-defining-an-enum.html +++ b/docs/ch06-01-defining-an-enum.html @@ -47,7 +47,7 @@
diff --git a/docs/ch06-02-match.html b/docs/ch06-02-match.html index 382b4b5..1fd5603 100644 --- a/docs/ch06-02-match.html +++ b/docs/ch06-02-match.html @@ -47,7 +47,7 @@
diff --git a/docs/ch06-03-if-let.html b/docs/ch06-03-if-let.html index 35dc9af..e02a81d 100644 --- a/docs/ch06-03-if-let.html +++ b/docs/ch06-03-if-let.html @@ -47,7 +47,7 @@
diff --git a/docs/ch07-00-modules.html b/docs/ch07-00-modules.html index 3d59bd0..301e7cf 100644 --- a/docs/ch07-00-modules.html +++ b/docs/ch07-00-modules.html @@ -47,7 +47,7 @@
diff --git a/docs/ch07-01-mod-and-the-filesystem.html b/docs/ch07-01-mod-and-the-filesystem.html index b38777a..068c139 100644 --- a/docs/ch07-01-mod-and-the-filesystem.html +++ b/docs/ch07-01-mod-and-the-filesystem.html @@ -47,7 +47,7 @@
diff --git a/docs/ch07-02-controlling-visibility-with-pub.html b/docs/ch07-02-controlling-visibility-with-pub.html index 2ff89b6..8db758a 100644 --- a/docs/ch07-02-controlling-visibility-with-pub.html +++ b/docs/ch07-02-controlling-visibility-with-pub.html @@ -47,7 +47,7 @@
diff --git a/docs/ch07-03-importing-names-with-use.html b/docs/ch07-03-importing-names-with-use.html index 69fc45d..4c46499 100644 --- a/docs/ch07-03-importing-names-with-use.html +++ b/docs/ch07-03-importing-names-with-use.html @@ -47,7 +47,7 @@
diff --git a/docs/ch08-00-common-collections.html b/docs/ch08-00-common-collections.html index 039e682..14f7254 100644 --- a/docs/ch08-00-common-collections.html +++ b/docs/ch08-00-common-collections.html @@ -47,7 +47,7 @@
diff --git a/docs/ch08-01-vectors.html b/docs/ch08-01-vectors.html index 8d6f005..4a877ec 100644 --- a/docs/ch08-01-vectors.html +++ b/docs/ch08-01-vectors.html @@ -47,7 +47,7 @@
diff --git a/docs/ch08-02-strings.html b/docs/ch08-02-strings.html index 88775c9..126e6af 100644 --- a/docs/ch08-02-strings.html +++ b/docs/ch08-02-strings.html @@ -47,7 +47,7 @@
diff --git a/docs/ch08-03-hash-maps.html b/docs/ch08-03-hash-maps.html index 1926ef8..2edfe2a 100644 --- a/docs/ch08-03-hash-maps.html +++ b/docs/ch08-03-hash-maps.html @@ -47,7 +47,7 @@
diff --git a/docs/ch09-00-error-handling.html b/docs/ch09-00-error-handling.html index d29a762..8706238 100644 --- a/docs/ch09-00-error-handling.html +++ b/docs/ch09-00-error-handling.html @@ -47,7 +47,7 @@
diff --git a/docs/ch09-01-unrecoverable-errors-with-panic.html b/docs/ch09-01-unrecoverable-errors-with-panic.html index e225054..14aed1c 100644 --- a/docs/ch09-01-unrecoverable-errors-with-panic.html +++ b/docs/ch09-01-unrecoverable-errors-with-panic.html @@ -47,7 +47,7 @@
diff --git a/docs/ch09-02-recoverable-errors-with-result.html b/docs/ch09-02-recoverable-errors-with-result.html index eb4835d..0d9048d 100644 --- a/docs/ch09-02-recoverable-errors-with-result.html +++ b/docs/ch09-02-recoverable-errors-with-result.html @@ -47,7 +47,7 @@
diff --git a/docs/ch09-03-to-panic-or-not-to-panic.html b/docs/ch09-03-to-panic-or-not-to-panic.html index a77f4e1..aa4cd0b 100644 --- a/docs/ch09-03-to-panic-or-not-to-panic.html +++ b/docs/ch09-03-to-panic-or-not-to-panic.html @@ -47,7 +47,7 @@
diff --git a/docs/ch10-00-generics.html b/docs/ch10-00-generics.html index adc0857..a4f1c22 100644 --- a/docs/ch10-00-generics.html +++ b/docs/ch10-00-generics.html @@ -47,7 +47,7 @@
diff --git a/docs/ch10-01-syntax.html b/docs/ch10-01-syntax.html index ff60aea..85b3e39 100644 --- a/docs/ch10-01-syntax.html +++ b/docs/ch10-01-syntax.html @@ -47,7 +47,7 @@
diff --git a/docs/ch10-02-traits.html b/docs/ch10-02-traits.html index 52a99b4..b512f79 100644 --- a/docs/ch10-02-traits.html +++ b/docs/ch10-02-traits.html @@ -47,7 +47,7 @@
diff --git a/docs/ch10-03-lifetime-syntax.html b/docs/ch10-03-lifetime-syntax.html index de782e2..fea4b83 100644 --- a/docs/ch10-03-lifetime-syntax.html +++ b/docs/ch10-03-lifetime-syntax.html @@ -47,7 +47,7 @@
diff --git a/docs/ch11-00-testing.html b/docs/ch11-00-testing.html index b10718a..509e391 100644 --- a/docs/ch11-00-testing.html +++ b/docs/ch11-00-testing.html @@ -47,7 +47,7 @@
diff --git a/docs/ch11-01-writing-tests.html b/docs/ch11-01-writing-tests.html index 2e87491..a6a3804 100644 --- a/docs/ch11-01-writing-tests.html +++ b/docs/ch11-01-writing-tests.html @@ -47,7 +47,7 @@
diff --git a/docs/ch11-02-running-tests.html b/docs/ch11-02-running-tests.html index 2dd650f..d5f8a1d 100644 --- a/docs/ch11-02-running-tests.html +++ b/docs/ch11-02-running-tests.html @@ -47,7 +47,7 @@
diff --git a/docs/ch11-03-test-organization.html b/docs/ch11-03-test-organization.html index b4d867e..a8e0f24 100644 --- a/docs/ch11-03-test-organization.html +++ b/docs/ch11-03-test-organization.html @@ -47,7 +47,7 @@
@@ -153,6 +153,67 @@ mod tests {

因为测试也不过是 Rust 代码而tests也只是另一个模块,我们完全可以在一个测试中导入并调用internal_adder。如果你并不认为私有函数应该被测试,Rust 也不会强迫你这么做。

集成测试

+

在 Rust 中,集成测试对于需要测试的库来说是完全独立。他们同其他代码一样使用库文件。他们的目的是测试库的个个部分结合起来能否正常工作。每个能正确运行的代码单元集成在一起也可能会出现问题,所以集成测试的覆盖率也是很重要的。

+

tests 目录

+

Cargo 支持位于 tests 目录中的集成测试。如果创建它并放入 Rust 源文件,Cargo 会将每一个文件当作单独的 crate 来编译。让我们试一试!

+

首先,在项目根目录创建一个 tests 目录,挨着 src 目录。接着新建一个文件 tests/integration_test.rs,并写入列表 11-7 中的代码:

+
+Filename: tests/integration_test.rs +
extern crate adder;
+
+#[test]
+fn it_adds_two() {
+    assert_eq!(4, adder::add_two(2));
+}
+
+
+

Listing 11-7: An integration test of a function in the adder crate

+
+
+

在开头使用了extern crate adder,单元测试中并不需要它。tests目录中的每一个测试文件都是完全独立的 crate,所以需要在每个文件中导入我们的库。这也就是为何tests是编写集成测试的绝佳场所:他们像任何其他用户那样,需要将库导入 crate 并只能使用公有 API。

+

这个文件中也不需要tests模块。除非运行测试否则整个文件夹都不会被编译,所以无需将任何部分标记为#[cfg(test)]。另外每个测试文件都被隔离进其自己的 crate 中,无需进一步隔离测试代码。

+

让我们运行集成测试,同样使用cargo test来运行:

+
$ cargo test
+   Compiling adder v0.1.0 (file:///projects/adder)
+     Running target/debug/deps/adder-abcabcabc
+
+running 1 test
+test tests::it_works ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+
+     Running target/debug/integration_test-952a27e0126bb565
+
+running 1 test
+test it_adds_two ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+
+   Doc-tests adder
+
+running 0 tests
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
+
+

现在有了三个部分的输出:单元测试、集成测试和文档测试。注意当在任何 src 目录的文件中增加单元测试时,单元测试部分的对应输出也会增加。增加集成测试文件中的测试函数也会对应增加输出。如果在 tests 目录中增加集成测试文件,则会增加更多集成测试部分:一个文件对应一个部分。

+

cargo test指定测试函数名称参数也会匹配集成测试文件中的函数。为了只运行某个特定集成测试文件中的所有测试,可以使用cargo test--test参数:

+
$ cargo test --test integration_test
+    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
+     Running target/debug/integration_test-952a27e0126bb565
+
+running 1 test
+test it_adds_two ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+
+

集成测试中的子模块

+

随着集成测试的增加,你可能希望在 tests 目录增加更多文件,例如根据测试的功能来将测试分组。正如我们之前提到的,这是可以的,Cargo 会将每一个文件当作一个独立的 crate。

+

最终,可能会有一系列在所有集成测试中通用的帮助函数,例如建立通用场景的函数。如果你将这些函数提取到 tests 目录的一个文件中,比如说 tests/common.rs,则这个文件将会像这个目录中的其他包含测试的 Rust 文件一样被编译进一个单独的 crate 中。它也会作为一个独立的部分出现在测试输出中。因为这很可能不是你所希望的,所以建议在子目录中使用 mod.rs 文件,比如 tests/common/mod.rs,来放置帮助函数。tests 的子目录不会被作为单独的 crate 编译或者作为单独的部分出现在测试输出中。

+

二进制 crate 的集成测试

+

如果项目是二进制 crate 并且只包含 src/main.rs 而没有 src/lib.rs,这样就不可能在 tests 创建集成测试并使用 extern crate 导入 src/main.rs 中的函数了。这也是 Rust 二进制项目明确采用 src/main.rs 调用 src/lib.rs 中逻辑的结构的原因之一。通过这种结构,集成测试就可以使用extern crate测试库 crate 中的主要功能,而如果这些功能没有问题的话,src/main.rs 中的少量代码也就会正常工作且不需要测试。

+

总结

+

Rust 的测试功能提供了一个确保即使改变代码函数也能继续以指定方式运行的途径。单元测试独立的验证库的每一部分并能够测试私有实现细节。集成测试则涉及多个部分结合起来时能否使用,并像其他代码那样测试库的公有 API。Rust 的类型系统和所有权规则可以帮助避免一些 bug,不过测试对于减少代码是否符合期望的逻辑 bug 是很重要的。

+

接下来让我们结合本章所学和其他之前章节的知识,在下一章一起编写一个项目!

@@ -164,6 +225,10 @@ mod tests { + +
@@ -174,6 +239,10 @@ mod tests { + +
diff --git a/docs/ch12-00-an-io-project.html b/docs/ch12-00-an-io-project.html new file mode 100644 index 0000000..af2ad7d --- /dev/null +++ b/docs/ch12-00-an-io-project.html @@ -0,0 +1,147 @@ + + + + + 一个 I/O 项目 - Rust 程序设计语言 简体中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

一个 I/O 项目

+
+

ch12-00-an-io-project.md +
+commit efd59dd0fe8e3658563fb5fd289af9d862e07a03

+
+

之前几个章节我们学习了很多知识。让我们一起运用这些新知识来构建一个项目。在这个过程中,我们还将学习到更多 Rust 标准库的内容。

+

那么我们应该写点什么呢?这得是一个利用 Rust 优势的项目。Rust 的一个强大的用途是命令行工具:Rust 的运行速度、安全性、“单二进制文件”输出和跨平台支持使得它称为这类工作的绝佳选择。所以我们将创建一个我们自己的经典命令行工具:grepgrep有着极为简单的应用场景,它完成如下工作:

+
    +
  1. 它获取一个文件和一个字符串作为参数。
  2. +
  3. 读取文件
  4. +
  5. 寻找文件中包含字符串参数的行
  6. +
  7. 打印出这些行
  8. +
+

另外,我们还将添加一个额外的功能:一个环境变量允许我们大小写不敏感的搜索字符串参数。

+

还有另一个很好的理由使用grep作为示例项目:Rust 社区的成员,Andrew Gallant,已经使用 Rust 创建了一个功能非常完整的grep版本。它叫做ripgrep,并且它非常非常快。这样虽然我们的grep将会非常简单,你也会掌握阅读现实生活中项目的基础知识。

+

这个项目将会结合之前所学的一些内容:

+
    +
  • 代码组织(使用第七章学习的模块)
  • +
  • vector 和字符串(第八章,集合)
  • +
  • 错误处理(第九章)
  • +
  • 合理的使用 trait 和生命周期(第十章)
  • +
  • 测试(第十一章)
  • +
+

另外,我还会简要的讲到闭包、迭代器和 trait 对象,他们分别会在第XX、YY和ZZ章详细介绍。

+

让我们一如既往的使用cargo new创建一个新项目:

+
$ cargo new --bin greprs
+     Created binary (application) `greprs` project
+$ cd greprs
+
+

我们版本的grep的叫做“greprs”,这样就不会迷惑用户让他们以为这就是可能已经在系统上安装了功能更完整的grep

+ +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/ch12-01-accepting-command-line-arguments.html b/docs/ch12-01-accepting-command-line-arguments.html new file mode 100644 index 0000000..af4a82f --- /dev/null +++ b/docs/ch12-01-accepting-command-line-arguments.html @@ -0,0 +1,154 @@ + + + + + 接受命令行参数 - Rust 程序设计语言 简体中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

接受命令行参数

+
+

ch12-01-accepting-command-line-arguments.md +
+commit 2d32840aae46d247250310219e8c7169c7349017

+
+

第一个任务是让greprs接受两个命令行参数。crates.io 上有一些现存的库可以帮助我们,不过因为我们正在学习,我们将自己实现一个。

+

我们需要调用一个 Rust 标准库提供的函数:std::env::args。这个函数返回一个传递给程序的命令行参数的迭代器iterator)。我们还未讨论到迭代器,第十三章会全面的介绍他们。但是对于我们的目的来说,使用他们并不需要知道多少技术细节。我们只需要明白两点:

+
    +
  1. 迭代器生成一系列的值。
  2. +
  3. 在迭代器上调用collect方法可以将其生成的元素转换为一个 vector。
  4. +
+

让我们试试列表 12-1 中的代码:

+
+Filename: src/main.rs +
use std::env;
+
+fn main() {
+    let args: Vec<String> = env::args().collect();
+    println!("{:?}", args);
+}
+
+
+

Listing 12-1: Collect the command line arguments into a vector and print them out

+
+
+ +

首先使用use语句来将std::env模块引入作用域。当函数嵌套了多于一层模块时,比如说std::env::args,通常使用use将父模块引入作用域,而不是引入其本身。env::args比单独的args要明确一些。当然,如果使用了多余一个std::env中的函数,我们也只需要一个use语句。

+

main函数的第一行,我们调用了env::args,并立即使用collect来创建了一个 vector。这里我们也显式的注明了args的类型:collect可以被用来创建很多类型的集合。Rust 并不能推断出我们需要什么类型,所以类型注解是必须的。在 Rust 中我们很少会需要注明类型,不过collect是就一个通常需要这么做的函数。

+

最后,我们使用调试格式:?打印出 vector。让我们尝试不用参数运行代码,接着用两个参数:

+
$ cargo run
+["target/debug/greprs"]
+
+$ cargo run needle haystack
+...snip...
+["target/debug/greprs", "needle", "haystack"]
+
+

你会注意一个有趣的事情:二进制文件的名字是第一个参数。其原因超出了本章介绍的范围

+ +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/ch12-02-reading-a-file.html b/docs/ch12-02-reading-a-file.html new file mode 100644 index 0000000..52c5f87 --- /dev/null +++ b/docs/ch12-02-reading-a-file.html @@ -0,0 +1,116 @@ + + + + + 读取文件 - Rust 程序设计语言 简体中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+ +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/ch12-03-improving-error-handling-and-modularity.html b/docs/ch12-03-improving-error-handling-and-modularity.html new file mode 100644 index 0000000..f7f48de --- /dev/null +++ b/docs/ch12-03-improving-error-handling-and-modularity.html @@ -0,0 +1,116 @@ + + + + + 增强错误处理和模块化 - Rust 程序设计语言 简体中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+ +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/ch12-04-testing-the-librarys-functionality.html b/docs/ch12-04-testing-the-librarys-functionality.html new file mode 100644 index 0000000..f6559c9 --- /dev/null +++ b/docs/ch12-04-testing-the-librarys-functionality.html @@ -0,0 +1,116 @@ + + + + + 测试库的功能 - Rust 程序设计语言 简体中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+ +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/ch12-05-working-with-environment-variables.html b/docs/ch12-05-working-with-environment-variables.html new file mode 100644 index 0000000..f735425 --- /dev/null +++ b/docs/ch12-05-working-with-environment-variables.html @@ -0,0 +1,116 @@ + + + + + 处理环境变量 - Rust 程序设计语言 简体中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+ +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/ch12-06-writing-to-stderr-instead-of-stdout.html b/docs/ch12-06-writing-to-stderr-instead-of-stdout.html new file mode 100644 index 0000000..b0729ab --- /dev/null +++ b/docs/ch12-06-writing-to-stderr-instead-of-stdout.html @@ -0,0 +1,108 @@ + + + + + 输出到`stderr`而不是`stdout` - Rust 程序设计语言 简体中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+ +
+ + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/index.html b/docs/index.html index 1944d9b..7c6e760 100644 --- a/docs/index.html +++ b/docs/index.html @@ -46,7 +46,7 @@
diff --git a/docs/print.html b/docs/print.html index bf98b58..76d5081 100644 --- a/docs/print.html +++ b/docs/print.html @@ -2,7 +2,7 @@ - 测试的组织结构 - Rust 程序设计语言 简体中文版 + 输出到`stderr`而不是`stdout` - Rust 程序设计语言 简体中文版 @@ -47,7 +47,7 @@
@@ -6038,6 +6038,136 @@ mod tests {

因为测试也不过是 Rust 代码而tests也只是另一个模块,我们完全可以在一个测试中导入并调用internal_adder。如果你并不认为私有函数应该被测试,Rust 也不会强迫你这么做。

集成测试

+

在 Rust 中,集成测试对于需要测试的库来说是完全独立。他们同其他代码一样使用库文件。他们的目的是测试库的个个部分结合起来能否正常工作。每个能正确运行的代码单元集成在一起也可能会出现问题,所以集成测试的覆盖率也是很重要的。

+

tests 目录

+

Cargo 支持位于 tests 目录中的集成测试。如果创建它并放入 Rust 源文件,Cargo 会将每一个文件当作单独的 crate 来编译。让我们试一试!

+

首先,在项目根目录创建一个 tests 目录,挨着 src 目录。接着新建一个文件 tests/integration_test.rs,并写入列表 11-7 中的代码:

+
+Filename: tests/integration_test.rs +
extern crate adder;
+
+#[test]
+fn it_adds_two() {
+    assert_eq!(4, adder::add_two(2));
+}
+
+
+

Listing 11-7: An integration test of a function in the adder crate

+
+
+

在开头使用了extern crate adder,单元测试中并不需要它。tests目录中的每一个测试文件都是完全独立的 crate,所以需要在每个文件中导入我们的库。这也就是为何tests是编写集成测试的绝佳场所:他们像任何其他用户那样,需要将库导入 crate 并只能使用公有 API。

+

这个文件中也不需要tests模块。除非运行测试否则整个文件夹都不会被编译,所以无需将任何部分标记为#[cfg(test)]。另外每个测试文件都被隔离进其自己的 crate 中,无需进一步隔离测试代码。

+

让我们运行集成测试,同样使用cargo test来运行:

+
$ cargo test
+   Compiling adder v0.1.0 (file:///projects/adder)
+     Running target/debug/deps/adder-abcabcabc
+
+running 1 test
+test tests::it_works ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+
+     Running target/debug/integration_test-952a27e0126bb565
+
+running 1 test
+test it_adds_two ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+
+   Doc-tests adder
+
+running 0 tests
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
+
+

现在有了三个部分的输出:单元测试、集成测试和文档测试。注意当在任何 src 目录的文件中增加单元测试时,单元测试部分的对应输出也会增加。增加集成测试文件中的测试函数也会对应增加输出。如果在 tests 目录中增加集成测试文件,则会增加更多集成测试部分:一个文件对应一个部分。

+

cargo test指定测试函数名称参数也会匹配集成测试文件中的函数。为了只运行某个特定集成测试文件中的所有测试,可以使用cargo test--test参数:

+
$ cargo test --test integration_test
+    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
+     Running target/debug/integration_test-952a27e0126bb565
+
+running 1 test
+test it_adds_two ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
+
+

集成测试中的子模块

+

随着集成测试的增加,你可能希望在 tests 目录增加更多文件,例如根据测试的功能来将测试分组。正如我们之前提到的,这是可以的,Cargo 会将每一个文件当作一个独立的 crate。

+

最终,可能会有一系列在所有集成测试中通用的帮助函数,例如建立通用场景的函数。如果你将这些函数提取到 tests 目录的一个文件中,比如说 tests/common.rs,则这个文件将会像这个目录中的其他包含测试的 Rust 文件一样被编译进一个单独的 crate 中。它也会作为一个独立的部分出现在测试输出中。因为这很可能不是你所希望的,所以建议在子目录中使用 mod.rs 文件,比如 tests/common/mod.rs,来放置帮助函数。tests 的子目录不会被作为单独的 crate 编译或者作为单独的部分出现在测试输出中。

+

二进制 crate 的集成测试

+

如果项目是二进制 crate 并且只包含 src/main.rs 而没有 src/lib.rs,这样就不可能在 tests 创建集成测试并使用 extern crate 导入 src/main.rs 中的函数了。这也是 Rust 二进制项目明确采用 src/main.rs 调用 src/lib.rs 中逻辑的结构的原因之一。通过这种结构,集成测试就可以使用extern crate测试库 crate 中的主要功能,而如果这些功能没有问题的话,src/main.rs 中的少量代码也就会正常工作且不需要测试。

+

总结

+

Rust 的测试功能提供了一个确保即使改变代码函数也能继续以指定方式运行的途径。单元测试独立的验证库的每一部分并能够测试私有实现细节。集成测试则涉及多个部分结合起来时能否使用,并像其他代码那样测试库的公有 API。Rust 的类型系统和所有权规则可以帮助避免一些 bug,不过测试对于减少代码是否符合期望的逻辑 bug 是很重要的。

+

接下来让我们结合本章所学和其他之前章节的知识,在下一章一起编写一个项目!

+

一个 I/O 项目

+
+

ch12-00-an-io-project.md +
+commit efd59dd0fe8e3658563fb5fd289af9d862e07a03

+
+

之前几个章节我们学习了很多知识。让我们一起运用这些新知识来构建一个项目。在这个过程中,我们还将学习到更多 Rust 标准库的内容。

+

那么我们应该写点什么呢?这得是一个利用 Rust 优势的项目。Rust 的一个强大的用途是命令行工具:Rust 的运行速度、安全性、“单二进制文件”输出和跨平台支持使得它称为这类工作的绝佳选择。所以我们将创建一个我们自己的经典命令行工具:grepgrep有着极为简单的应用场景,它完成如下工作:

+
    +
  1. 它获取一个文件和一个字符串作为参数。
  2. +
  3. 读取文件
  4. +
  5. 寻找文件中包含字符串参数的行
  6. +
  7. 打印出这些行
  8. +
+

另外,我们还将添加一个额外的功能:一个环境变量允许我们大小写不敏感的搜索字符串参数。

+

还有另一个很好的理由使用grep作为示例项目:Rust 社区的成员,Andrew Gallant,已经使用 Rust 创建了一个功能非常完整的grep版本。它叫做ripgrep,并且它非常非常快。这样虽然我们的grep将会非常简单,你也会掌握阅读现实生活中项目的基础知识。

+

这个项目将会结合之前所学的一些内容:

+
    +
  • 代码组织(使用第七章学习的模块)
  • +
  • vector 和字符串(第八章,集合)
  • +
  • 错误处理(第九章)
  • +
  • 合理的使用 trait 和生命周期(第十章)
  • +
  • 测试(第十一章)
  • +
+

另外,我还会简要的讲到闭包、迭代器和 trait 对象,他们分别会在第XX、YY和ZZ章详细介绍。

+

让我们一如既往的使用cargo new创建一个新项目:

+
$ cargo new --bin greprs
+     Created binary (application) `greprs` project
+$ cd greprs
+
+

我们版本的grep的叫做“greprs”,这样就不会迷惑用户让他们以为这就是可能已经在系统上安装了功能更完整的grep

+

接受命令行参数

+
+

ch12-01-accepting-command-line-arguments.md +
+commit 2d32840aae46d247250310219e8c7169c7349017

+
+

第一个任务是让greprs接受两个命令行参数。crates.io 上有一些现存的库可以帮助我们,不过因为我们正在学习,我们将自己实现一个。

+

我们需要调用一个 Rust 标准库提供的函数:std::env::args。这个函数返回一个传递给程序的命令行参数的迭代器iterator)。我们还未讨论到迭代器,第十三章会全面的介绍他们。但是对于我们的目的来说,使用他们并不需要知道多少技术细节。我们只需要明白两点:

+
    +
  1. 迭代器生成一系列的值。
  2. +
  3. 在迭代器上调用collect方法可以将其生成的元素转换为一个 vector。
  4. +
+

让我们试试列表 12-1 中的代码:

+
+Filename: src/main.rs +
use std::env;
+
+fn main() {
+    let args: Vec<String> = env::args().collect();
+    println!("{:?}", args);
+}
+
+
+

Listing 12-1: Collect the command line arguments into a vector and print them out

+
+
+ +

首先使用use语句来将std::env模块引入作用域。当函数嵌套了多于一层模块时,比如说std::env::args,通常使用use将父模块引入作用域,而不是引入其本身。env::args比单独的args要明确一些。当然,如果使用了多余一个std::env中的函数,我们也只需要一个use语句。

+

main函数的第一行,我们调用了env::args,并立即使用collect来创建了一个 vector。这里我们也显式的注明了args的类型:collect可以被用来创建很多类型的集合。Rust 并不能推断出我们需要什么类型,所以类型注解是必须的。在 Rust 中我们很少会需要注明类型,不过collect是就一个通常需要这么做的函数。

+

最后,我们使用调试格式:?打印出 vector。让我们尝试不用参数运行代码,接着用两个参数:

+
$ cargo run
+["target/debug/greprs"]
+
+$ cargo run needle haystack
+...snip...
+["target/debug/greprs", "needle", "haystack"]
+
+

你会注意一个有趣的事情:二进制文件的名字是第一个参数。其原因超出了本章介绍的范围

diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 7e91e19..57fb372 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -53,4 +53,12 @@ - [测试](ch11-00-testing.md) - [编写测试](ch11-01-writing-tests.md) - [运行测试](ch11-02-running-tests.md) - - [测试的组织结构](ch11-03-test-organization.md) \ No newline at end of file + - [测试的组织结构](ch11-03-test-organization.md) + +- [一个 I/O 项目](ch12-00-an-io-project.md) + - [接受命令行参数](ch12-01-accepting-command-line-arguments.md) + - [读取文件](ch12-02-reading-a-file.md) + - [增强错误处理和模块化](ch12-03-improving-error-handling-and-modularity.md) + - [测试库的功能](ch12-04-testing-the-librarys-functionality.md) + - [处理环境变量](ch12-05-working-with-environment-variables.md) + - [输出到`stderr`而不是`stdout`](ch12-06-writing-to-stderr-instead-of-stdout.md) \ No newline at end of file diff --git a/src/ch11-03-test-organization.md b/src/ch11-03-test-organization.md index 883c33c..75736aa 100644 --- a/src/ch11-03-test-organization.md +++ b/src/ch11-03-test-organization.md @@ -120,3 +120,91 @@ Listing 11-6: Testing a private function ### 集成测试 +在 Rust 中,集成测试对于需要测试的库来说是完全独立。他们同其他代码一样使用库文件。他们的目的是测试库的个个部分结合起来能否正常工作。每个能正确运行的代码单元集成在一起也可能会出现问题,所以集成测试的覆盖率也是很重要的。 + +#### *tests* 目录 + +Cargo 支持位于 *tests* 目录中的集成测试。如果创建它并放入 Rust 源文件,Cargo 会将每一个文件当作单独的 crate 来编译。让我们试一试! + +首先,在项目根目录创建一个 *tests* 目录,挨着 *src* 目录。接着新建一个文件 *tests/integration_test.rs*,并写入列表 11-7 中的代码: + + +
+Filename: tests/integration_test.rs + +```rust,ignore +extern crate adder; + +#[test] +fn it_adds_two() { + assert_eq!(4, adder::add_two(2)); +} +``` + +
+ +Listing 11-7: An integration test of a function in the `adder` crate + +
+
+ +在开头使用了`extern crate adder`,单元测试中并不需要它。`tests`目录中的每一个测试文件都是完全独立的 crate,所以需要在每个文件中导入我们的库。这也就是为何`tests`是编写集成测试的绝佳场所:他们像任何其他用户那样,需要将库导入 crate 并只能使用公有 API。 + +这个文件中也不需要`tests`模块。除非运行测试否则整个文件夹都不会被编译,所以无需将任何部分标记为`#[cfg(test)]`。另外每个测试文件都被隔离进其自己的 crate 中,无需进一步隔离测试代码。 + +让我们运行集成测试,同样使用`cargo test`来运行: + +``` +$ cargo test + Compiling adder v0.1.0 (file:///projects/adder) + Running target/debug/deps/adder-abcabcabc + +running 1 test +test tests::it_works ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + + Running target/debug/integration_test-952a27e0126bb565 + +running 1 test +test it_adds_two ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + + Doc-tests adder + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured +``` + +现在有了三个部分的输出:单元测试、集成测试和文档测试。注意当在任何 *src* 目录的文件中增加单元测试时,单元测试部分的对应输出也会增加。增加集成测试文件中的测试函数也会对应增加输出。如果在 *tests* 目录中增加集成测试**文件**,则会增加更多集成测试部分:一个文件对应一个部分。 + +为`cargo test`指定测试函数名称参数也会匹配集成测试文件中的函数。为了只运行某个特定集成测试文件中的所有测试,可以使用`cargo test`的`--test`参数: + +``` +$ cargo test --test integration_test + Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs + Running target/debug/integration_test-952a27e0126bb565 + +running 1 test +test it_adds_two ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured +``` + +#### 集成测试中的子模块 + +随着集成测试的增加,你可能希望在 `tests` 目录增加更多文件,例如根据测试的功能来将测试分组。正如我们之前提到的,这是可以的,Cargo 会将每一个文件当作一个独立的 crate。 + +最终,可能会有一系列在所有集成测试中通用的帮助函数,例如建立通用场景的函数。如果你将这些函数提取到 *tests* 目录的一个文件中,比如说 *tests/common.rs*,则这个文件将会像这个目录中的其他包含测试的 Rust 文件一样被编译进一个单独的 crate 中。它也会作为一个独立的部分出现在测试输出中。因为这很可能不是你所希望的,所以建议在子目录中使用 *mod.rs* 文件,比如 *tests/common/mod.rs*,来放置帮助函数。*tests* 的子目录不会被作为单独的 crate 编译或者作为单独的部分出现在测试输出中。 + +#### 二进制 crate 的集成测试 + +如果项目是二进制 crate 并且只包含 *src/main.rs* 而没有 *src/lib.rs*,这样就不可能在 *tests* 创建集成测试并使用 `extern crate` 导入 *src/main.rs* 中的函数了。这也是 Rust 二进制项目明确采用 *src/main.rs* 调用 *src/lib.rs* 中逻辑的结构的原因之一。通过这种结构,集成测试**就可以**使用`extern crate`测试库 crate 中的主要功能,而如果这些功能没有问题的话,*src/main.rs* 中的少量代码也就会正常工作且不需要测试。 + +## 总结 + +Rust 的测试功能提供了一个确保即使改变代码函数也能继续以指定方式运行的途径。单元测试独立的验证库的每一部分并能够测试私有实现细节。集成测试则涉及多个部分结合起来时能否使用,并像其他代码那样测试库的公有 API。Rust 的类型系统和所有权规则可以帮助避免一些 bug,不过测试对于减少代码是否符合期望的逻辑 bug 是很重要的。 + +接下来让我们结合本章所学和其他之前章节的知识,在下一章一起编写一个项目! \ No newline at end of file diff --git a/src/ch12-00-an-io-project.md b/src/ch12-00-an-io-project.md new file mode 100644 index 0000000..54a22aa --- /dev/null +++ b/src/ch12-00-an-io-project.md @@ -0,0 +1,38 @@ +# 一个 I/O 项目 + +> [ch12-00-an-io-project.md](https://github.com/rust-lang/book/blob/master/src/ch12-00-an-io-project.md) +>
+> commit efd59dd0fe8e3658563fb5fd289af9d862e07a03 + +之前几个章节我们学习了很多知识。让我们一起运用这些新知识来构建一个项目。在这个过程中,我们还将学习到更多 Rust 标准库的内容。 + +那么我们应该写点什么呢?这得是一个利用 Rust 优势的项目。Rust 的一个强大的用途是命令行工具:Rust 的运行速度、安全性、“单二进制文件”输出和跨平台支持使得它称为这类工作的绝佳选择。所以我们将创建一个我们自己的经典命令行工具:`grep`。`grep`有着极为简单的应用场景,它完成如下工作: + +1. 它获取一个文件和一个字符串作为参数。 +2. 读取文件 +3. 寻找文件中包含字符串参数的行 +4. 打印出这些行 + +另外,我们还将添加一个额外的功能:一个环境变量允许我们大小写不敏感的搜索字符串参数。 + +还有另一个很好的理由使用`grep`作为示例项目:Rust 社区的成员,Andrew Gallant,已经使用 Rust 创建了一个功能非常完整的`grep`版本。它叫做`ripgrep`,并且它非常非常快。这样虽然我们的`grep`将会非常简单,你也会掌握阅读现实生活中项目的基础知识。 + +这个项目将会结合之前所学的一些内容: + +- 代码组织(使用第七章学习的模块) +- vector 和字符串(第八章,集合) +- 错误处理(第九章) +- 合理的使用 trait 和生命周期(第十章) +- 测试(第十一章) + +另外,我还会简要的讲到闭包、迭代器和 trait 对象,他们分别会在第XX、YY和ZZ章详细介绍。 + +让我们一如既往的使用`cargo new`创建一个新项目: + +```text +$ cargo new --bin greprs + Created binary (application) `greprs` project +$ cd greprs +``` + +我们版本的`grep`的叫做“greprs”,这样就不会迷惑用户让他们以为这就是可能已经在系统上安装了功能更完整的`grep`。 \ No newline at end of file diff --git a/src/ch12-01-accepting-command-line-arguments.md b/src/ch12-01-accepting-command-line-arguments.md new file mode 100644 index 0000000..512e61e --- /dev/null +++ b/src/ch12-01-accepting-command-line-arguments.md @@ -0,0 +1,52 @@ +## 接受命令行参数 + +> [ch12-01-accepting-command-line-arguments.md](https://github.com/rust-lang/book/blob/master/src/ch12-01-accepting-command-line-arguments.md) +>
+> commit 2d32840aae46d247250310219e8c7169c7349017 + +第一个任务是让`greprs`接受两个命令行参数。crates.io 上有一些现存的库可以帮助我们,不过因为我们正在学习,我们将自己实现一个。 + +我们需要调用一个 Rust 标准库提供的函数:`std::env::args`。这个函数返回一个传递给程序的命令行参数的**迭代器**(*iterator*)。我们还未讨论到迭代器,第十三章会全面的介绍他们。但是对于我们的目的来说,使用他们并不需要知道多少技术细节。我们只需要明白两点: + +1. 迭代器生成一系列的值。 +2. 在迭代器上调用`collect`方法可以将其生成的元素转换为一个 vector。 + +让我们试试列表 12-1 中的代码: + +
+Filename: src/main.rs + +```rust +use std::env; + +fn main() { + let args: Vec = env::args().collect(); + println!("{:?}", args); +} +``` + +
+ +Listing 12-1: Collect the command line arguments into a vector and print them out + +
+
+ + + +首先使用`use`语句来将`std::env`模块引入作用域。当函数嵌套了多于一层模块时,比如说`std::env::args`,通常使用`use`将父模块引入作用域,而不是引入其本身。`env::args`比单独的`args`要明确一些。当然,如果使用了多余一个`std::env`中的函数,我们也只需要一个`use`语句。 + +在`main`函数的第一行,我们调用了`env::args`,并立即使用`collect`来创建了一个 vector。这里我们也显式的注明了`args`的类型:`collect`可以被用来创建很多类型的集合。Rust 并不能推断出我们需要什么类型,所以类型注解是必须的。在 Rust 中我们很少会需要注明类型,不过`collect`是就一个通常需要这么做的函数。 + +最后,我们使用调试格式`:?`打印出 vector。让我们尝试不用参数运行代码,接着用两个参数: + +``` +$ cargo run +["target/debug/greprs"] + +$ cargo run needle haystack +...snip... +["target/debug/greprs", "needle", "haystack"] +``` + +你会注意一个有趣的事情:二进制文件的名字是第一个参数。其原因超出了本章介绍的范围 \ No newline at end of file diff --git a/src/ch12-02-reading-a-file.md b/src/ch12-02-reading-a-file.md new file mode 100644 index 0000000..e69de29 diff --git a/src/ch12-03-improving-error-handling-and-modularity.md b/src/ch12-03-improving-error-handling-and-modularity.md new file mode 100644 index 0000000..e69de29 diff --git a/src/ch12-04-testing-the-librarys-functionality.md b/src/ch12-04-testing-the-librarys-functionality.md new file mode 100644 index 0000000..e69de29 diff --git a/src/ch12-05-working-with-environment-variables.md b/src/ch12-05-working-with-environment-variables.md new file mode 100644 index 0000000..e69de29 diff --git a/src/ch12-06-writing-to-stderr-instead-of-stdout.md b/src/ch12-06-writing-to-stderr-instead-of-stdout.md new file mode 100644 index 0000000..e69de29