5分钟速读之Rust权威指南(二十)

901 阅读4分钟

编写测试

在前端开发的时候,要做单元测试的时候,一般使用使用第三方jest库,但是在rust开发时,rust已经帮开发者内置了可测试功能

如何编写测试

不管在任何语言中的测试,都不外乎以下三个步骤:

  1. 准备所需的数据或状态。
  2. 调用需要测试的代码。
  3. 断言运行结果与我们所期望的一致。

测试函数的构成

将#[test]添加到关键字fn的上一行便可以将函数转变为测试函数,然后使用cargo test命令来运行测试:

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

在测试中如果出现panic!会导致测试失败:

#[test]
fn another() {
  panic!("出错了")
}

assert!检查结果是否为true:

#[test]
fn is_true() {
  let is_eq = 2 > 1;
  assert!(is_eq);
}

assert_eq!检查两个数据是否相同:

#[test]
fn is_eq() {
  // assert_eq!和assert_ne!宏分别使用了==和!=运算符来进行判断,
  // 并在断言失败时使用调试输出格式{:?}将参数值打印出来。
  // 这意味着它们的参数必须同时实现PartialEq和Debug这两个trait。
  #[derive(Debug, PartialEq)]
  struct Rect {
    width: u32,
    height: u32,
  }
  let rect1 = Rect {
    width: 150,
    height: 100,
  };
  let rect2 = Rect { ..rect1 };
  assert_eq!(rect1, rect2);
}

assert_ne!检查两个数据是否不相同:

#[test]
fn is_ne() {
  fn add(a: i32, b: i32) -> i32 {
    a + b
  }
  assert_ne!(add(1, 2), 4)
}

添加自定义的错误提示信息:

#[test]
fn custom_error() {
  let a = 1;
  let b = 2;
  
  // 断言函数在必要的参数之后的所有参数都将传递给format!宏,用于打印
  assert!(a > b, "{}应该大于{}", a, b);
  assert_eq!(a, a, "{}应该等于", a);
  assert_ne!(a, b, "{}应该不等于{}", a, b);
}

在函数上标记should_panic属性来断言应该触发异常:

#[test]
#[should_panic]
fn should_error() {
  panic!("应该会出错")
}

确认抛出的异常是预期的,而不是其他位置的异常:

#[test]
#[should_panic(expected = "小于")]
fn should_error() {
  let a = 1;
  let b = 2;
  if a > b {
    panic!("a大于b")
  } else {
    panic!("a小于b") // expected中的`小于`在这个panic!中的文字是匹配的
  }
}

使用Result<T, E>编写测试,这种测试方式可以用来代替assert系列的测试方式,函数返回Ok时表示测试通过:

#[test]
fn use_result() -> Result<(), String> {
  let a = 1;
  let b = 1;
  if a == b {
    Result::Ok(()) // Ok的参数需要是空元组
  } else {
    Result::Err(format!("{} != {}", a, b))
  }
}

控制测试的运行方式

运行测试的参数:

cargo test --help

控制测试运行方式,通过--来分隔命令参数:

cargo test -- --help

cargo默认开启并行测试,用于减少测试时间,这就要为多个测试之前不能彼此先后依赖,可以用参数控制具体的线程数:

cargo test -- --test-threads=1

在测试通过时,我们无法看到代码中的println!等标注输出,因为会被Rust捕获,使用参数来控制Rust不进行捕获标注输出:

cargo test -- --nocapture

可以只运行部分测试用例来减少测试时间,例如测试所有匹配字符串is_的测试用例:

cargo test is_

运行单个测试:

cargo test it_works

注意:不能运行多个测试:

cargo test it_works is_eq

通过显式指定来忽略某些测试:

#[test]
#[ignore]
fn should_ignore() {
  panic!("这个错误不会被抛出,因为should_ignore测试不会被运行")
}

如果想要只运行这些被忽略的测试:

cargo test -- --ignored

测试组织结构

单元测试

单元测试一般和业务代码都写在同一文件中,只是通过新建一个测试模块来标识:

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

// 标记为测试的模块将会在cargo build后移除
// 另外,我们不需要对集成测试标注#[cfg(test)],因为集成测试本身就放置在独立的目录中,rust自然清楚build的时候不进行处理。
// cfg: configuration,这里的意思是只有在test时才会被处理
#[cfg(test)]
mod testaaa {
  use super::add;
  #[test]
  pub fn test_add() {
    assert_eq!(add(1, 2), 3)
  }
}

集成测试

集成测试是完全位于src目录之外的tests文件夹中:

mkdir tests

测试一个外部包adder:

use adder::add_one;
#[test]
fn test_add() {
  assert_eq!(add_one(1), 2)
}

测试(包含单元测试、集成测试、文档测试) cargo test:

cargo test

只运行某个集成测试:

cargo test --test [集成测试文件名]

集成测试公共函数需要放置在tests/common/mod.rs文件中,否则放置在其他位置也都将被rust识别成集成测试用例:

mod common; // 引用使用公共包
#[test]
fn test_add() {
  // 使用公共包中的setup函数
  common::setup();
  assert_eq!(add_one(1), 2)
}