Skip to content
📖 Auto 版 Rust Book本书目录

编写自动化测试 ​

Edsger W. Dijkstra 在 1972 年的文章"The Humble Programmer"中说:"程序测试可以非常有效地证明 bug 的存在,但对于证明 bug 不存在却无能为力。"这不意味着我们不应该尽可能多地测试!

程序中的_正确性_是指代码按照我们的意图执行的程度。Auto 非常关注正确性 —— 类型系统、所有权规则和借用检查器在编译时就捕获了许多 bug。但类型系统无法捕获所有问题。这就是自动化测试发挥作用的地方。

Auto 的测试系统与 Rust 紧密对齐,因为 Auto 转译为 Rust 并使用相同的测试运行器来运行测试。本章涵盖 Auto 中可用的测试机制。

如何编写测试 ​

_测试_是验证非测试代码按预期运行的函数。测试函数通常执行以下三个操作:

  1. 设置所需的数据或状态。
  2. 运行你要测试的代码。
  3. 断言结果符合预期。

测试函数的结构 ​

Auto 中的测试是用 #[test] 标注的函数。当你运行 automan test 时,转译器将代码编译为 Rust 并运行 cargo test,它会构建一个测试运行器二进制文件来执行被标注的函数。

使用 `assert_eq!` 的测试函数

#[test] 属性将函数标记为测试。assert_eq! 宏检查两个值是否相等。如果不相等,测试会 panic 并失败。

用 assert! 检查结果 ​

assert! 宏验证条件是否为 true。如果条件为 false,测试失败:

auto
#[test]
fn larger_can_hold_smaller() {
    let larger = Rectangle(8, 7)
    let smaller = Rectangle(5, 1)
    assert(larger.can_hold(&smaller))
}

添加自定义失败消息 ​

你可以在 assert! 和 assert_eq! 中添加自定义消息,使失败更容易调试:

带有自定义失败消息的测试

当这个测试失败时,会打印自定义消息,显示 result 的实际值 —— 使调试变得更容易。

用 #[should_panic] 检查 Panic ​

要测试代码在某些条件下会 panic,使用 #[should_panic] 属性:

auto
#[test]
#[should_panic]
fn it_panics() {
    panic("expected panic")
}

你还可以用 expected 检查特定的 panic 消息:

auto
#[test]
#[should_panic(expected = "less than or equal to 100")]
fn greater_than_100() {
    Guess.new(200)
}

在测试中使用 !T(Result) ​

测试可以返回 !T(Result)而不是 panic。这让你可以在测试函数中使用 .!(错误传播):

使用 `#[should_panic]` 和 `!T` 返回类型的测试

注意:fn valid_guess() ! 转译为 fn valid_guess() -> Result<(), String>。 这让你可以在测试中使用 ?(通过 .!)进行错误传播。

测试语法速查 ​

AutoRust用途
#[test]#[test]将函数标记为测试
assert_eq(a, b)assert_eq!(a, b)断言相等
assert_ne(a, b)assert_ne!(a, b)断言不等
assert(cond)assert!(cond)断言条件为真
#[should_panic]#[should_panic]测试应该 panic
#[should_panic(expected = "...")]#[should_panic(expected = "...")带特定消息的 panic
fn test() !fn test() -> Result<(), String>返回错误而非 panic

控制测试的运行方式 ​

使用 automan test(它会调用转译后的 Rust 代码上的 cargo test)运行测试:

console
$ automan test

并行或顺序运行测试 ​

测试默认并行运行。要逐个运行:

console
$ automan test -- --test-threads=1

显示输出 ​

要查看通过测试的 print() 输出:

console
$ automan test -- --show-output

运行测试的子集 ​

运行名称包含特定模式的测试:

console
$ automan test add       # 运行所有名称包含 "add" 的测试

运行单个测试:

console
$ automan test it_adds_two

忽略耗时测试 ​

使用 #[ignore] 跳过耗时的测试:

auto
#[test]
#[ignore]
fn expensive_test() {
    // 耗时较长的代码
}

只运行被忽略的测试:

console
$ automan test -- --ignored

测试组织 ​

Auto 遵循与 Rust 相同的测试组织方式:

单元测试 ​

单元测试放在与被测代码相同的文件中,在 #[cfg(test)] 标注的 tests 模块内:

依赖模块示例

此示例依赖多文件模块,不能在书页内运行——请到 Playground

auto
fn internal_adder(a int, b int) int {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn internal() {
        assert_eq(internal_adder(2, 2), 4)
    }
}

单元测试可以测试私有函数,因为 tests 模块是子模块,可以通过 use super::* 访问父模块的私有项。

集成测试 ​

集成测试放在项目根目录的 tests/ 目录中。tests/ 中的每个文件被编译为单独的 crate,只测试公共 API:

my_project/
├── auto.toml
├── src/
│   └── lib.rs
└── tests/
    └── integration_test.rs

集成测试使用 use 从你的库中导入:

依赖模块示例

此示例依赖多文件模块,不能在书页内运行——请到 Playground

auto
use my_project::add_two

#[test]
fn it_adds_two() {
    assert_eq(add_two(2), 4)
}

总结 ​

Auto 的测试系统通过转译利用了 Rust 成熟的测试基础设施。要点如下:

概念Auto命令
运行所有测试—automan test
单个测试#[test]automan test test_name
过滤测试—automan test pattern
单线程运行—automan test -- --test-threads=1
显示输出—automan test -- --show-output
运行被忽略的测试#[ignore]automan test -- --ignored

测试宏(assert_eq!、assert!、assert_ne!)在 Auto 和 Rust 中工作方式完全相同。Auto 的 fn test() ! 提供了一种简洁的方式,使用与整个代码库相同的 !T 语法来编写传播错误的测试。

现在让我们结合前面章节学到的知识来构建一个项目!

基于 MIT 许可发布。