Rust入坑指南:步步为营

俗话说:“测试写得好,奖金少不了。” bash

有经验的开发人员一般会经过单元测试来保证代码基本逻辑的正确性。若是你是一名新手开发者,而且还没体会到单元测试的好处,那么建议你先读一下我以前的一篇文章代码洁癖系列(七):单元测试的地位函数

写单元测试通常须要三个步骤:单元测试

  1. 准备测试用例,测试用例要能覆盖尽量多的代码
  2. 执行须要测试的代码
  3. 判断结果,是不是你但愿获得的结果

了解了这些之后,咱们就来看看在Rust中应该怎么写单元测试。测试

首先咱们创建一个library项目命令行

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

而后在src/lib.rs文件中开始写测试代码线程

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

此时在命令行运行cargo test就会获得测试结果3d

测试结果1

能够看到,结果显示,Rust运行了一项测试而且测试经过。后面的Doc-tests咱们先放下,之后再聊。code

固然,这并非咱们常见的测试,在平常开发中,咱们一般是先写咱们的业务代码而后再对各个函数进行单元测试,最后还会对某个模块进行集成测试。那么咱们就来模拟一下平常开发过程当中应该如何来写测试。blog

单元测试

咱们仍然是用上面的项目,先来在src/lib.rs中写一段“业务代码”开发

pub fn add_two(a: i32) -> i32 {
    internal_adder(a, 2)
}

fn internal_adder(a: i32, b: i32) -> i32 {
    a + b
}

这是一段很是简单的代码,对外暴露的函数只是一个加2的功能,内部调用了一个两数相加的函数。如今咱们就对这个内部函数作一个单元测试。

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

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

在测试模块中,若是想要使用咱们业务代码中的函数,就须要经过use super::*;将其引入可用范围。接着,仍是执行cargo test,测试结果与刚才相似。

测试结果2

测了半天全是经过的没什么意思,单元测试真正的做用是要发现代码中的问题,因此咱们来尝试一个错误的试一下。假设咱们但愿2+2等于5。

测试结果3

这里咱们的assert_eq!左右不相等,引发了线程恐慌,所以致使测试失败。结果中给出了失败的缘由,引发失败的位置,而且有一句提示:note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. 咱们按照这个提示,设置变量RUST_BACKTRACE=1,此时再执行cargo test

错误栈

Rust就会将错误栈打印出来,根据结果提示,这并非完整的错误栈,咱们还能够将RUST_BACKTRACE设置为full来查看更加详细的信息。这里我就不作演示了。

集成测试

接下来咱们再演示一下集成测试。咱们一般将集成测试单独放到一个目录中,在lib.rs文件中,rust识别测试mod的名称是tests,一样的,咱们在src下建立tests目录。tests目录下就是咱们的全部集成测试代码。

测试目录

如图,integration_test是咱们测试代码的文件,common目录下的mod.rs文件中是一些集成测试必要的配置。这里咱们只是放了一个空的setup函数。

在集成测试中,咱们就要像正常他人使用咱们的代码时那样来进行测试,首先须要将咱们的mod引入到可用范围,固然还须要加上common的mod。

use adder;

mod common;

#[tests]
fn it_adds_two() {
    common::setup();
    assert_eq!(4, adder::add_two(2));
}

接着就能够测试咱们对外暴露的函数了。

集成测试

ok,集成测试的方法咱们也掌握了。如今来看看一直被咱们忽略的Doc-tests吧。

文档测试

咱们已经知道,Rust中的注释是双斜线//,像咱们刚刚写的library代码,若是想要把它发布到crate.io上让别人使用,那么咱们就须要增长相应的文档,这里文档的每行都应该是三斜线///开头,而文档中也应该放一些例子供他人参考。

/// Adds two to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = adder::add_two(arg);
///
/// assert_eq!(7, answer);
/// ```
pub fn add_two(a: i32) -> i32 {
    internal_adder(a, 2)
}

如今我给add_two函数加上了文档,咱们再次执行cargo test命令。

文档测试

如今咱们就明白了,Doc-tests测试就是运行咱们文档中的例子。

经常使用特性

到目前为止,咱们已经知道了在Rust中如何写测试代码了。接下来咱们再来了解几个比较经常使用的特性。

运行指定的测试代码

咱们在开发过程当中确定不会每次都去跑全量的单元测试,那样太浪费时间了。一般是咱们开发完一个功能以后,编写对应的单元测试,而后单独跑这个测试。那么Rust中能不能单独跑一个单元测试呢?答案是确定的。

相信细心的同窗已经发现了,Rust测试结果中,是针对每一个测试单独统计结果,而且每一个测试都有本身的名字,像咱们前面写的it_worksinternal。假设咱们的代码中同时存在这两个函数,若是你想要单独跑internal这一个测试,就可使用cargo test internal命令。

你也可使用这种方法来执行多个名称相似的测试,假如咱们有名称为internal_a的测试,那么执行cargo test internal命令时它也会被执行。

忽略某个测试

当咱们有一个测试执行时间很是长的时候,咱们通常不会轻易去执行,这时若是你想要执行多个测试,除了用咱们上面提到的方法,去指定不一样的名称列表之外。还能够把这个测试忽略掉。

如今我不想执行internal测试了,只须要对代码进行以下改动:

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

这时再来运行测试,结果如图所示。

忽略测试

咱们发现此时internal测试已经被忽略了。

测试异常状况

除了测试代码逻辑正常的状况,咱们有时还须要测试一些异常状况,好比接收到非法参数时程序可否返回咱们但愿看到的异常。

咱们首先来看一下如何测试程序返回异常信息。

Rust为咱们提供了一个叫作should_panic的注解。咱们可使用它来测试程序是否返回异常:

pub fn add_two(a: i32) -> i32 {
    internal_adder(a, 2)
}

fn internal_adder(a: i32, b: i32) -> i32 {
    if a < 0 {
        panic!("a should bigger than 0");
    }
    a + b
}

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

    #[test]
    #[should_panic]
    fn internal() {
        assert_eq!(4, internal_adder(-2, 2));
    }
}

此时咱们运行测试时就会发现internal测试经过,由于它发生了线程恐慌,这是咱们但愿看到的结果。

测试异常

另外,咱们还能够再指定咱们具体指望的异常,那么就能够在should_panic后面加上expected参数。

#[test]
#[should_panic(expected = "a should be positive")]
fn internal() {
  assert_eq!(4, internal_adder(-2, 2));
}

你们能够自行运行一下这段测试代码看看效果。

总结

文中我向你们介绍了在Rust中如何进行单元测试、集成测试,还有比较特殊的文档测试。最后还介绍了3种常见的测试特性。

最后想友情提醒你们一下,在开发过程当中,不要写完一堆功能后再开始写单元测试,这时你颇有可能会由于测试代码过于繁琐而放弃。建议你们每写一个功能,随即开始进行单元测试,这样也能当即看到本身的代码的执行效果,提升成就感。这就是所谓的“步步为营”。

相关文章
相关标签/搜索