# 编写自动化测试 Edsger W.Dijkstra在1972年的文章[谦卑的程序员]("The Humble Programmer")中说到"软件测试 是证明bug存在的有效方法, 而证明其不存在时则显得令人绝望的不足。"("Program testing can a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence."),这并不意味着我们不应该尽可能的进行系统的测试软件! Rust可以在编译时进行类型检查和借用检查,Rust所不能检查的时这个函数是否会准确的完成我们 所期望的工作。 ## [如何编写测试](../examples/how_to_write_test.rs) 测试函数体通常执行的三分步骤: 1. 设置任何所需的数据或状态 2. 运行需要进行测试的代码 3. 检查结果是否符合预期 不能对使用Result的测试使用#[should_panic]注解。为了断言一个操作 返回Err成员,***不要***使用对Result的问号表达式,而是使用 assert(value.is_err()) ## [控制测试如何运行](../examples/how_to_control_test_run.rs) 1. cargo run 会编译代码并运行生成的二进制文件 2. cargo test 在测试模式下编译代码并运行生成的测试二进制文件 > 生成的二进制文件的默认行为时并行的运行所有测试,并截获测试运行过程中的产生的输出,阻止他们被显示出来,使得阅读测试结果相关的内容变得更容易。 3. 可以将一部分命令行参数传递给cargo test,而将另一部分参数传递给生成的二进制文件。为了分隔这两种参数,需要先列出传递给cargo test的参数,接着是分隔符 -- ,再之后是传递给测试二进制文件的参数。 > 1. 运行cargo test --help 会提示cargo test的有关参数 > 2. 运行cargo test -- --help 可以提示在分隔符 -- 之后使用的有关参数 ### 并行或连续的运行测试 当运行多个测试的时候,rust默认使用多线程来并行运行。这意味着测试会更快地运行完毕,所以可以更快的得到代码能否工作的反馈。因为测试时同时运行的,应该确保测试之间不能相互依赖,或者依赖任何共享的状态,包括依赖共享的资源,比如当前工作的目录或者环境变量。 如果不希望测试并行运行,或者想要更加精确的控制线程的数量,可以传递--test-threads 参数和希望使用线程的数量给测试二进制文件。例如: ```shell cargo test -- --test-threads=1 ``` 这里将测试线程设置为1,告诉程序不要使用任何并行机制。这也会比并行运行花费更多的时间,不过在有共享的状态时,测试就不会潜在的相互干扰了。 ### 命令示例 1. 显示测试通过的用例的函数的输出 ```shell cargo test -- --show-output ``` 2. 通过名字来运行不同的测试,只要包含关键字的用例都会运行 ```shell cargo test keyword ``` 3. 只运行被忽略的测试用例 ```shell cargo test -- --ignored ``` 4. 组合使用 ```shell cargo test -- --ignored --show-output ``` ## [测试的组织结构](../examples/how_to_organize_tests.rs) 测试是一个复杂的概念,而且不同的开发者也采用不同的技术和组织。Rust社区倾向于根据测试的两个主要分类来考虑问题:**单元测试(unit test)**与**集成测试(integration tests)**。 1. 单元测试倾向于更小而更集中,在隔离的环境中一次测试一个模块,或者是测试私有接口。 2. 集成测试对于你的库来说则完全是外部的。它们与其他外部代码一样,通过相同的方法使用你的代码,只测试共有接口而且每个测试都有可能会测试多个模块。 ### 单元测试 单元测试的目的是在与其他部分隔离的环境中测试每一个单元的代码,以便于快速而准确的验证某个单元的代码功能是否符合预期。 单元测试与他们要测试的代码共同存放位于src目录下**相同**的文件中。规范是在每个文件中创建包含测试函数的tests模块,并使用cfg(test)标注模块。 测试模块和#[cfg(test)] 测试模块的#[cfg(test)]标注告诉Rust只在执行cargo test时才编译和运行测试代码,而在运行cargo build时不这么做,这在只希望构件库时可以节省编译时间,减少编译产生的文件的大小。 而在集成测试时因为位于另一个文件夹中,所以他们并不需要#[cfg(test)]标注。 ### 测试私有函数 Rust的私有性规则确实允许但并不强迫你测试私有函数。 ### 集成测试 集成测试对需要测试的库来说是完全外部的。同其他使用库的代码一样使用库文件,也就意味着他们只能调用一部分中的公有API。集成测试的目的是测试库的多个部分能否在一起正常工作。 #### [tests目录](../tests/integration_test.rs) 为了编写集成测试,需要在项目创建一个tests目录,与src同级。cargo知道如何去寻找这个目录中集成测试文件。接着可以随意在这个目录中创建任意多的测试文件,Cargo会将每一个文件当作单独的crate来编译。 可以使用cargo test 的 --test 后跟文件的名称来运行某个特定集成测试文件中的所有测试: ```shell cargo test --test integration_test_file_name ``` #### 集成测试中的子模块 为了不让`common`出现在测试输出中,可以创建tests/common/mod.rs,而不是创建tests/common.rs。这是一种Rust命名规范,这样可以告诉Rust不要将`common`看作一个集成测试文件。在其他的集成测试文件中可以调用`common`中的函数。 ### 二进制crate的集成测试 如果项目是二进制crate并且只包含`src/main.rs`,而没有src/lib.rs,这样就不可能在tests目录中创建集成测试并使用use语句导入src/main.rs中定义的函数。只有库crate才会向其他crate暴露可以调用和使用的函数;二进制crate只意在单独运行。 这就是Rust二进制项目明确采用src/main.rs调用src/lib.rs中的逻辑的原因之一。通过这种结构,集成测试就可以通过use测试库crate中的主要功能,而如果这些重要的功能没有问题的话,src/main.rs中的少量代码也就会正常工作且不需要测试。