测试(函数)

  • 测试:
    • 函数
    • 验证非测试代码的功能是否和预期一致
  • 测试函数体(通常)执行的 3 个操作

    • 准备数据/状态
    • 运行被测试的代码
    • 断言(Assert)结果

      解剖测试函数

  • 测试函数需要使用 test 属性(attribute) 进行标注

    • attribute 就是一段 Rust 代码的元数据
    • 在函数上加 #[test],可把函数变成测试函数

      运行测试

  • 使用 cargo test 命令运行所有测试函数

    • Rust 会构建一个 test runner 可执行文件
      • 它会运行标注了 test 的函数,并报告其运行是否成功
  • 当使用 cargo 创建 library 项目的时候,会生成一个 test module,里面有一个 test 函数
    • 你可以添加任意数据的 test module 或函数

创建库项目,并进入文件夹 cargo new adder --lib && cd adder 打开 lib.rs 运行 cargo test

  1. #[cfg(test)]
  2. mod tests {
  3. #[test]
  4. fn it_works() {
  5. assert_eq!(2 + 2, 4);
  6. }
  7. }
  8. // running 1 test
  9. // test tests::it_works ... ok
  10. //
  11. // test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
  12. // 1 个通过 0 个失败 0 个忽略 0 个性能测试 0 个被过滤的测试
  13. // Doc-tests adder
  14. // 文档测试结果,保证文档总会和实际代码同步
  15. // running 0 tests
  16. //
  17. // test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

测试失败

  • 测试函数 panic 就表示失败
  • 每个测试运行在一个新线程
  • 当主线程看见某个测试线程挂掉了,那个测试标记为失败了 ```rust

    [cfg(test)]

    mod tests {

    [test]

    fn exploration() {

    1. assert_eq!(2 + 2, 4);

    }

    [test]

    fn another() {

    1. panic!("Make this test fail")

    } }

// running 2 tests // test tests::exploration … ok // test tests::another … FAILED // // failures: // // —— tests::another stdout —— // thread ‘tests::another’ panicked at ‘Make this test fail’, src/lib.rs:10:9 // note: run with RUST_BACKTRACE=1 environment variable to display a backtrace // // // failures: // tests::another // // test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s // // error: test failed, to rerun pass ‘—lib’

  1. <a name="DNcUS"></a>
  2. # 断言(Assert)
  3. <a name="FDRSc"></a>
  4. ## 使用 assert! 宏检查测试结果
  5. - assert! 宏,来自标准库,用来确定某个状态是否为 true
  6. - true:测试通过
  7. - false:调用 panic,测试失败
  8. ```rust
  9. #[derive(Debug)]
  10. pub struct Rectangle {
  11. length: u32,
  12. width: u32,
  13. }
  14. impl Rectangle {
  15. pub fn can_hold(&self, other: &Rectangle) -> bool {
  16. self.length > other.length && self.width > other.width
  17. }
  18. }
  19. #[cfg(test)]
  20. mod tests {
  21. // test 模块与其他模块没有任何区别
  22. use super::*; // 将外部模块所以内容导入到 test 模块
  23. #[test]
  24. fn larger_can_hold_smaller() {
  25. // 准备数据
  26. let larger = Rectangle {
  27. length: 8,
  28. width: 7,
  29. };
  30. let smaller = Rectangle {
  31. length: 5,
  32. width: 1,
  33. };
  34. // 执行待测试代码
  35. assert!(larger.can_hold(&smaller));
  36. }
  37. }

使用 assert_eq! 和 assert_ne! 测试相等性

  • 都来自标准库
  • 判断两个参数是否 相等不等
  • 实际上,它们使用的就是 ==!= 运算符
  • 断言失败:自动打印出两个参数的值
    • 使用 debug 格式打印参数
    • 要求参数实现了 PartialEq 和 Debug Traits(所有的基本类型和标准库里大部分类型都实现了) ```rust pub fn add_two(a: i32) -> i32 { a + 2 }

[cfg(test)]

mod tests { use super::*;

  1. #[test]
  2. fn if_adds_two() {
  3. assert_eq!(4, add_two(2)); // 期待的值和执行结果位置不要求
  4. }

}

  1. <a name="fTTR0"></a>
  2. # 自定义错误消息
  3. <a name="yxXDd"></a>
  4. ## 添加自定义错误消息
  5. - 可以向 assert!、assert_eq!、assert_ne! 添加可选的自定义消息
  6. - 这些自定义消息和失败消息都会打印出来
  7. - assert!: 第 1 参数必填,自定义消息作为第 2 个参数
  8. - assert_eq! 和 assert_ne!: 前 2 个参数必填,自定义消息作为第3个参数
  9. - 自定义消息参数会被传递给 format! 宏,可以使用 {} 占位符
  10. ```rust
  11. pub fn greeting(name: &str) -> String {
  12. format!("Hello")
  13. }
  14. #[cfg(test)]
  15. mod tests {
  16. use super::*;
  17. #[test]
  18. fn greetings_contain_name() {
  19. let result = greeting("Carol");
  20. assert!(
  21. result.contains("Carol"),
  22. "Greeting didn't contain name, value was '{}'",
  23. result
  24. );
  25. }
  26. }

用 should_panic 检查恐慌

验证错误处理的情况

  • 测试除了验证代码的返回值是否正确,还需验证代码是否如预期的处理了发生错误的情况
  • 可验证代码在特定情况下是否发生了 panic
  • should_panic 属性 (attribute):

    • 函数 panic:测试通过
    • 函数没有 panic:测试失败

      1. pub struct Guess {
      2. value: u32,
      3. }
      4. impl Guess {
      5. pub fn new(value: u32) -> Guess {
      6. if value < 1 || value > 100 {
      7. panic!("Guess value must be between 1 and 100, got {}.", value)
      8. }
      9. Guess { value }
      10. }
      11. }
      12. #[cfg(test)]
      13. mod tests {
      14. use super::*;
      15. #[test]
      16. #[should_panic]
      17. fn greater_than_100() {
      18. Guess::new(200);
      19. }
      20. }

      让 should_panic 更精确

      ```rust pub struct Guess { value: u32, } impl Guess { pub fn new(value: u32) -> Guess { if value < 1 {

      1. panic!(
      2. "Guess value must be greater than or equal to 1, got {}.",
      3. value
      4. )

      } else if value > 100 {

      1. panic!(
      2. "Guess value must be less than or equal to 100, got {}.",
      3. value
      4. )

      } Guess { value } } }

      [cfg(test)]

      mod tests { use super::*;

      [test]

      [should_panic(expected = “Guess value must be less than or equal to 100”)]

      fn greater_than_100() { Guess::new(0); } }

// note: panic did not contain expected string // panic message: "Guess value must be greater than or equal to 1, got 0.", // expected substring: "Guess value must be less than or equal to 100"

  1. <a name="GkGd0"></a>
  2. # 在测试中使用 Result<T, E>
  3. - 无需 panic,可使用 Result<T, E> 作为返回类型编写测试:
  4. - 返回 Ok:测试通过
  5. - 返回 Err:测试失败
  6. ```rust
  7. #[cfg(test)]
  8. mod tests {
  9. #[test]
  10. fn it_workd() -> Result<(), String> {
  11. if 2 + 3 == 4 {
  12. Ok(())
  13. } else {
  14. Err(String::from("two plus two does not equal four"))
  15. }
  16. }
  17. }
  • 注意:不要在使用 Result 编写的测试上标注 #[should_panic]

    因为在运行失败时会直接返回 Err 不会发生 panic