|
|
|
|
# Automated Tests
|
|
|
|
|
|
|
|
|
|
## How to Write Tests
|
|
|
|
|
|
|
|
|
|
测试函数的主体通常执行以下三个操作:
|
|
|
|
|
|
|
|
|
|
1.设置任何需要的数据或状态。
|
|
|
|
|
|
|
|
|
|
2.运行要测试的代码。
|
|
|
|
|
|
|
|
|
|
3.断言的结果是你所期望的。
|
|
|
|
|
|
|
|
|
|
Rust专门为编写执行这些操作的测试提供的特性,其中包括test属性、一些宏和should_panic属性。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 测试函数解剖
|
|
|
|
|
|
|
|
|
|
最简单情况下,rust中的测试是一个用test属性注释的函数。属性是关于 Rust 代码片段的元数据(derive 也是一个属性);再要测试的函数(fn)前,添加#[test]。使用**cargo test**,Rust会构建一个测试运行程序二进制文件,运行带注释的函数,并报告每个测试函数是通过还是失败。
|
|
|
|
|
|
|
|
|
|
每当我们用 Cargo 创建一个新的库项目时,都会自动为我们生成一个带有测试功能的测试模块。
|
|
|
|
|
|
|
|
|
|
使用cargo test会产生如下结果:
|
|
|
|
|
|
|
|
|
|
![image-20230302225047715](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230302225047715.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
多个测试用例时(有对有错,可以是抛出的panic)结果如下:
|
|
|
|
|
|
|
|
|
|
![image-20230302230225599](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230302230225599.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Checking Results with the assert! Macro
|
|
|
|
|
|
|
|
|
|
assert! 宏,是标准库中提供的,主要用于判断条件和预期结果是否相同。相同(true)时无事发生,否则抛出一个panic。
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
use super::*;
|
|
|
|
|
// tests模块是一个内部模块,所以我们需要将外部模块中测试的代码引入内部模块的范围。
|
|
|
|
|
// 我们在这里使用了一个glob,所以我们在外层模块中定义的任何东西都可以用于这个测试模块。
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
对rectangle结构使用assert(正确情况),如下所示:
|
|
|
|
|
|
|
|
|
|
![image-20230302235254167](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230302235254167.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
错误情况:
|
|
|
|
|
|
|
|
|
|
![image-20230303000942014](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230303000942014.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
![image-20230309152409191](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309152409191.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
![image-20230309151658572](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309151658572.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
![image-20230309151916594](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309151916594.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
![image-20230309152409191](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309152409191.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
![image-20230309151658572](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309151658572.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
![image-20230309151916594](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309151916594.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Testing Equality with the assert_eq! and assert_ne! Macros
|
|
|
|
|
|
|
|
|
|
assert!宏用于执行 == 操作。再此基础上,标准库还提供了一对宏 assert_eq!和assert_ne! 分别比较相等或不相等的两个论点。如果断言失败,它们还会**打印两个值**,这使得更容易看到测试失败的原因;相反,**assert!宏**仅指示它为==表达式获取了一个假值,而**不打印**导致该假值的值。
|
|
|
|
|
|
|
|
|
|
使用assert_eq!宏,断言失败时:
|
|
|
|
|
|
|
|
|
|
![image-20230309154720180](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309154720180.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
使用assert_nq!宏,断言失败时:
|
|
|
|
|
|
|
|
|
|
![image-20230309155053448](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309155053448.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**assert_eq!和assert_ne!**宏分别使用运算符**==和!=**。当断言失败时,这些宏使用调试格式打印它们的参数,这意味着要比较的值必须实现**PartialEq**和**debug**特征。所有基本类型和大多数标准库类型都实现了这些特征。对于自己定义的结构体和枚举,需要实现PartialEq来断言这些类型的相等性。当断言失败时,还需要实现Debug来打印值。**\#[derive(PartialEq, Debug)]。**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Adding Custom Failure Messages
|
|
|
|
|
|
|
|
|
|
可以将要与**失败消息**一起打印的自定义消息添加为`assert!`、`assert_eq!`和`assert_ne!`宏的可选参数。
|
|
|
|
|
|
|
|
|
|
![image-20230309161947925](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309161947925.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
`assert_eq!`和`assert_ne!`时写法:
|
|
|
|
|
|
|
|
|
|
![image-20230309162455307](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309162455307.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Checking for Panics with should_panic
|
|
|
|
|
|
|
|
|
|
检查代码是否如所期望的那样处理错误。使用**should_panic**属性,如果函数内的代码**出现panic**,则测试**通过**;如果函数内的代码**不恐慌**,则测试**失败**。
|
|
|
|
|
|
|
|
|
|
当没有panic时,效果如下:
|
|
|
|
|
|
|
|
|
|
![image-20230309165534578](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309165534578.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
should_panic 即使测试恐慌的原因与我们预期的不同,should_panic测试也会通过。可通过添加一个可选的预期参数,让should_panic测试更准确。
|
|
|
|
|
|
|
|
|
|
![image-20230310134325891](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310134325891.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
失败情况:
|
|
|
|
|
|
|
|
|
|
![image-20230310134601301](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310134601301.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Using Result<T, E> in Tests
|
|
|
|
|
|
|
|
|
|
测试失败时会产生恐慌,可以通过 Result<T,E> 返回一个Err, 使恐慌消失。
|
|
|
|
|
|
|
|
|
|
![image-20230310142453605](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310142453605.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 控制测试的运行方式
|
|
|
|
|
|
|
|
|
|
默认情况下,由 cargo test 生成的二进制文件会**并行**运行所有测试,并**捕获**测试运行期间生成的**输出**,以便更容易阅读与测试结果相关的输出信息。但是,您可以使用命令行选项来更改此默认行为。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Running Tests in Parallel or Consecutively
|
|
|
|
|
|
|
|
|
|
当你运行多个测试时,默认情况下它们会使用**线程并行运行**,这意味着它们完成测试的速度更快,你可以更快地得到反馈。因为测试同时运行,所以你必须确保你的测试不依赖于彼此或任何共享状态,包括共享环境,例如当前工作目录或环境变量。
|
|
|
|
|
|
|
|
|
|
默认情况下,`cargo test` 会根据可用的 CPU 核心数量自动选择并发运行测试的线程数。具体来说,它会为每个 CPU 核心分配一个线程,最多不会超过8个线程。
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
$ cargo test -- --test-threads=1
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
测试线程数设为1,即不使用并行性。测试时间会更多,但如果用例间共享状态,则测试不会相互干扰。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Showing Function Output
|
|
|
|
|
|
|
|
|
|
默认情况下,如果**测试通过**,Rust 的测试库会捕获任何打印到标准输出的内容。
|
|
|
|
|
|
|
|
|
|
可使用 $ cargo test -- --show-output
|
|
|
|
|
|
|
|
|
|
![image-20230310145558520](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310145558520.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
使用 $ cargo test -- --nocapture时:
|
|
|
|
|
|
|
|
|
|
![image-20230310145404690](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310145404690.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
相对而言 show-output 界面更好。
|
|
|
|
|
|
|
|
|
|
`--show-output` 选项用于**显示**测试运行期间的输出信息。`--nocapture` 选项用于**禁止捕获**测试运行期间的输出信息。如果同时使用这两个选项,`--show-output` 选项会覆盖 `--nocapture` 选项。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Running a Subset of Tests by Name
|
|
|
|
|
|
|
|
|
|
可以指定要运行的部分代码。会按名称进行匹配,匹配多少运行多少。cargo test [名称],只要测试用例名称包含提供的字段,就会运行(多个或单个)。
|
|
|
|
|
|
|
|
|
|
当名称设为add时:
|
|
|
|
|
|
|
|
|
|
![image-20230310151923207](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310151923207.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Filtering to Run Multiple Tests
|
|
|
|
|
|
|
|
|
|
\#[ignore] 属性,使测试时排除他们。
|
|
|
|
|
|
|
|
|
|
![image-20230310152114535](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310152114535.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
`cargo test -- --ignored` 和 `cargo test -- --include-ignored` 都是用来运行被标记为`ignored`的测试的。
|
|
|
|
|
|
|
|
|
|
`cargo test -- --ignored` 运行被标记为`ignored`的测试,但是不会运行没有被标记为`ignored`的测试。这个选项是用来运行已知失败的测试或者还没有完成的测试的。也就是说,它只运行被标记为`ignored`的测试,而不管其他的测试是否通过。
|
|
|
|
|
|
|
|
|
|
例如,如果你有一个测试被标记为`ignored`,并且在这个测试通过之前你不想运行其他测试,你可以使用`cargo test -- --ignored`来只运行这个测试。
|
|
|
|
|
|
|
|
|
|
![image-20230310152809699](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310152809699.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
相反,`cargo test -- --include-ignored` 会运行所有测试,包括那些被标记为`ignored`的测试。这个选项是用来在某些情况下,你需要知道所有测试的状态,包括已知失败的测试或还没有完成的测试的时候使用的。
|
|
|
|
|
|
|
|
|
|
![image-20230310152735856](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310152735856.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Test Organization
|
|
|
|
|
|
|
|
|
|
Rust社区认为测试主要分为两类:单元测试和集成测试。单元测试较小且更集中,每次单独测试**一个模块**,并且可以测试**私有接口**。集成测试则完全在库外部,并且与任何其他外部代码相同的方式使用代码,只使用**公共接口**,并且可能在每个测试中使用**多个模块**。
|
|
|
|
|
|
|
|
|
|
### 单元测试
|
|
|
|
|
|
|
|
|
|
单元测试的目的是在与其他代码**隔离**的情况下测试每个代码单元。传统做法是在每个文件中创建名为**tests的模块**来包含测试函数,并用`cfg(test)`注释该模块。
|
|
|
|
|
|
|
|
|
|
在测试模块上的 `#[cfg(test)]` 注解告诉 Rust 仅在运行 `cargo test` 时编译和运行测试代码,而不在运行 `cargo build` 时编译。而因为集成测试在不同的目录中,它们不需要 `#[cfg(test)]` 注解。 cfg属性代表configuration,它告诉Rust只有在给定某个配置选项时才应该包含下面的项,在本例中,配置选项是test。
|
|
|
|
|
|
|
|
|
|
`cfg` 属性可以使用多个参数指定编译条件,其中一些常见的参数如下:
|
|
|
|
|
|
|
|
|
|
- `test`:仅在运行测试时编译
|
|
|
|
|
- `debug_assertions`:仅在使用 `cargo build` 或 `cargo run` 命令的 `--debug` 模式下编译
|
|
|
|
|
- `unix` 或 `windows`:仅在目标操作系统为 Unix 或 Windows 时编译
|
|
|
|
|
- `target_arch` 和 `target_os`:仅在目标 CPU 架构和操作系统匹配时编译
|
|
|
|
|
|
|
|
|
|
#### 测试私有函数
|
|
|
|
|
|
|
|
|
|
![image-20230310160912780](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310160912780.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 集成测试
|
|
|
|
|
|
|
|
|
|
集成测试只能调用属于您库的公共 API 的函数。目的是测试您的库的许多部分是否可以正确协同工作。不需要添加**\#[cfg(test)]**,Cargo对tests目录进行了特殊处理,仅在运行Cargo测试时才编译该目录中的文件。
|
|
|
|
|
|
|
|
|
|
#### 测试目录
|
|
|
|
|
|
|
|
|
|
测试目录在项目目录顶层,紧挨着src。目录名称必须为tests。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
adder
|
|
|
|
|
├── Cargo.lock
|
|
|
|
|
├── Cargo.toml
|
|
|
|
|
├── src
|
|
|
|
|
│ └── lib.rs
|
|
|
|
|
└── tests
|
|
|
|
|
└── integration_test.rs
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
如果单元测试失败,则不会有任何集成和文档测试的输出,因为这些测试只有在所有单元测试都通过时才会运行。按顺序来的,如果集成测试出错,那么不会有文档测试输出。
|
|
|
|
|
|
|
|
|
|
运行测试无误时结果如下:
|
|
|
|
|
|
|
|
|
|
![image-20230310163621820](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310163621820.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
集成测试也可以指定名称来运行:
|
|
|
|
|
|
|
|
|
|
![image-20230310165323146](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310165323146.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
运行特定集成测试文件中的所有测试,`cargo test --test 完整文件名`
|
|
|
|
|
|
|
|
|
|
![image-20230310164959908](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310164959908.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 集成测试中子模块
|
|
|
|
|
|
|
|
|
|
集成测试可以根据它们测试的功能将测试函数分组。同时当存在多个集成测试文件时,可以提取出相同的行为放入公共模块。
|
|
|
|
|
|
|
|
|
|
##### 集成测试中添加通用模块:
|
|
|
|
|
|
|
|
|
|
错误位置:
|
|
|
|
|
|
|
|
|
|
![image-20230310171134280](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310171134280.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
正确位置:
|
|
|
|
|
|
|
|
|
|
![image-20230310171717119](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310171717119.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
运行图(不含show-output):
|
|
|
|
|
|
|
|
|
|
![image-20230310171944280](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310171944280.png)
|
|
|
|
|
|
|
|
|
|
使用旧的 Rust 命名约定,即文件名为 `mod.rs` 的文件被视为包含该目录的模块的**主要文件**,而不是一个单独的集成测试文件。因此,Rust **不会将其视为一个集成测试文件**。这种约定在较旧的 Rust 代码中很常见,因此 Rust 保留了对这种方式的支持,以保持向后兼容性。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
项目目录:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
├── Cargo.lock
|
|
|
|
|
├── Cargo.toml
|
|
|
|
|
├── src
|
|
|
|
|
│ └── lib.rs
|
|
|
|
|
└── tests
|
|
|
|
|
├── common
|
|
|
|
|
│ └── mod.rs
|
|
|
|
|
└── integration_test.rs
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 针对二进制包的集成测试
|
|
|
|
|
|
|
|
|
|
如果项目中只包含一个 `src/main.rs` 文件而没有 `src/lib.rs` 文件,我们无法在 `tests` 目录中创建集成测试,并使用 `use` 语句将 `src/main.rs` 中定义的函数引入作用域。这是因为**只有库箱(library crate)才会公开其他箱可以使用的函数,而二进制箱本身可以单独运行**。因此,Rust 项目通常将**重要逻辑**放在 `src/lib.rs` 文件中,并在 `src/main.rs` 文件中**调用**这些逻辑,以此为基础来构建二进制箱。
|
|
|
|
|
|
|
|
|
|
**二进制箱可以进行单元测试**。在二进制箱中,可以将测试放在 `src/main.rs` 文件的底部,就像在库中将测试放在与源码文件相同的文件中一样。您可以使用 `#[cfg(test)]` 属性来标记测试函数,以便它们只在运行 `cargo test` 命令时进行编译和运行。请注意,因为二进制箱的目的是作为独立的可执行文件运行,而不是作为库被其他crate使用,因此在二进制箱中编写的测试通常会更注重对**集成测试的覆盖**,而不是对库代码进行单元测试。
|
|
|
|
|
|
|
|
|
|
单元测试:
|
|
|
|
|
|
|
|
|
|
![image-20230312135712101](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230312135712101.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
集成测试(失败):
|
|
|
|
|
|
|
|
|
|
![image-20230312140138211](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230312140138211.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
集成测试加上lib(成功):
|
|
|
|
|
|
|
|
|
|
![image-20230312140255410](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230312140255410.png)
|
|
|
|
|
|