Rust之自动化测试(一):如何编写测试

开发环境

  • Windows 10
  • Rust 1.71.1

 

  • VS Code 1.81.1

 项目工程

这里继续沿用上次工程rust-demo

编写自动化测试

Edsger W. Dijkstra在他1972年的文章《谦逊的程序员》中说,“程序测试可以是一种非常有效的方法来显示错误的存在,但它对于显示它们的不存在是完全不够的。”这并不意味着我们不应该尽可能多地尝试测试! 

我们程序的正确性是指我们的代码在多大程度上做了我们想要它做的事情。Rust的设计高度关注程序的正确性,但正确性很复杂,不容易证明。Rust的类型系统承担了这个负担的很大一部分,但是类型系统不能捕获所有的东西。因此,Rust包含了对编写自动化软件测试的支持。

假设我们编写了一个函数add_two,它将传递给它的任何数字加2。这个函数的签名接受一个整数作为参数,并返回一个整数作为结果。当我们实现和编译这个函数时,Rust会进行所有的类型检查和借用检查,就像你到目前为止所学的那样,以确保我们不会向这个函数传递一个String值或一个无效的引用。但是Rust不能检查这个函数是否能准确地达到我们的目的,即返回参数加2,而不是参数加10或参数减50!这就是测试的由来。

我们可以编写一些测试来断言,例如,当我们将3传递给add_two函数时,返回值是5。每当我们对代码进行更改时,我们都可以运行这些测试,以确保任何现有的正确行为都没有改变。

测试是一项复杂的技能:虽然我们无法在一章中涵盖如何编写好的测试的每个细节,但我们将讨论Rust测试工具的机制。我们将讨论编写测试时可用的注释和宏,为运行测试提供的默认行为和选项,以及如何将测试组织成单元测试和集成测试。

如何编写测试

测试是Rust函数,它验证非测试代码是否以预期的方式运行。测试函数的主体通常执行这三个动作:

  1. 设置任何需要的数据或状态。
  2. 运行您想要测试的代码。
  3. 断言结果是你所期望的。

让我们来看看Rust专门为编写执行这些操作的测试提供的特性,包括test属性、一些宏和should_panic属性。

测试函数的剖析

最简单地说,Rust中的测试是一个用test属性注释的函数。属性是关于Rust代码片段的元数据;一个例子是我们在第5章中对结构使用的derive属性。要将函数更改为测试函数,请在fn之前的行中添加#[test]。当您使用cargo test命令运行您的测试时,Rust会构建一个测试运行二进制文件,运行带注释的函数并报告每个测试函数是通过还是失败。

每当我们用Cargo创建一个新的库项目时,就会自动为我们生成一个包含测试功能的测试模块。这个模块为您提供了一个编写测试的模板,这样您就不必在每次开始一个新项目时都去查找精确的结构和语法。您可以添加任意多的额外测试函数和测试模块!  

在实际测试任何代码之前,我们将通过模板测试来探索测试工作的某些方面。然后,我们将编写一些真实世界的测试,调用我们编写的一些代码,并断言其行为是正确的。 

让我们创建一个名为adder的新库项目,它将添加两个数:

$ cargo new adder --lib
     Created library `adder` project
$ cd adder

 adder库中src/lib.rs文件的内容应该如示例11-1所示。

文件名:src/lib.rs

#[cfg(test)]
mod tests {         
    #[test]                       // 测试
    fn it_works() {
        let result = 2 + 2;
        assert_eq!(result, 4);
    }
}

示例11-1:由cargo new自动生成的测试模块和函数

现在,让我们忽略上面两行,把注意力集中在函数上。注意#[test]注释:这个属性表明这是一个测试函数,所以测试运行人员知道将这个函数视为一个测试。在tests模块中,我们还可能有非测试函数来帮助设置常见的场景或执行常见的操作,所以我们总是需要指出哪些函数是测试。

示例函数体使用了assert_eq!宏来断言包含2和2相加result的结果等于4。这个断言作为一个典型测试格式的例子。让我们运行它来看看这个测试是否通过。 

cargo test命令运行我们项目中的所有测试,如示例11-2所示。

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.57s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

示例11-2:运行自动生成的测试的输出 

 Cargo编译并运行了测试。我们看到runing1 test的行。下一行显示了生成的测试函数的名称,名为it_works,运行该测试的结果是ok的。总体总结test result: ok。意味着所有的测试都通过了,而写着1 passed; 0 failed总计测试通过或失败的次数。 

0 measured测量统计值用于测量性能的基准测试。在撰写本文时,基准测试只在夜间Rust中可用。

Doc-tests adder开始的测试输出的下一部分是任何文档测试的结果。我们还没有任何文档测试,但Rust可以编译任何出现在我们API文档中的代码示例。这个特性有助于保持您的文档和代码同步!现在,我们将忽略Doc-tests输出。

让我们开始根据自己的需要定制测试。首先将it_works函数的名称改为不同的名称,例如exploration,如下所示:

文件名:src/lib.rs

#[cfg(test)]
mod tests {
    #[test]
    fn exploration() {
        assert_eq!(2 + 2, 4);
    }
}

然后再次运行cargo test。现在输出显示的是exploration而不是it_works:

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.59s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 1 test
test tests::exploration ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

 现在我们将添加另一个测试,但这次我们将做一个失败的测试!当测试函数出现问题时,测试就会失败。每个测试都在一个新线程中运行,当主线程发现一个测试线程已经死亡时,该测试就会被标记为失败。在第9章中,我们谈到了恐慌的最简单的方法是如何打电话给panic!宏观。输入新的测试作为一个名为another的函数,这样你的src/lib.rs文件看起来如示例11-3所示。

文件名:src/lib.rs

#[cfg(t
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值