十、Rust 程序测试

243 阅读3分钟

十、Rust 程序测试

1. Rust 中的测试

  • Rust 中,测试函数就是一个带有 test 属性注解的函数或方法

    // 创建一个 lib 项目时,默认生成的一个模板
    #[cfg(test)]
    mod tests {
        #[test] // 表明这是一个测试函数
        fn it_works() {
            let result = 2 + 2;
            assert_eq!(result, 4); // 一个断言宏
        }
    }
    
    
  • 在运行测试用例的过程中,当测试函数中出现 panic 时,测试就失败了

  • Rust 内置的测试模块,还会运行文档注释里的示例代码,这是其他很多编程语言的测试工具所不具备的能力

  • 在了解用于测试的内置工具之前,需要了解一个概念:可被比较测试的值

    • 可被比较的值必需实现了 PartialEqDebug trait
  • 用于测试的相关宏

    • assert!(success: bool)
      • 判断传入的布尔值是否为 true,如果为 true 则测试通过
    • assert_eq!($left:expr, $right:expr $(,)?)
      • 两个参数对应了两个表达式
      • 判断两个表达式的结果是否相等。如果相等,则测试通过;反之,则测试不通过
      • 如果测试不通过,会打印出两个表达式的值
    • assert_ne!($left:expr, $right:expr $(,)?)
      • 两个参数对应了两个表达式
      • 判断两个表达式的结果是否不等。效果与 assert_eq! 相反,如果不等,则测试通过;反之,则测试不通过
  • 用于测试的相关属性

    • #[should_panic]

      • 用于测试 panic 的结果是否正确

        // 来自官方教程的一个示例
        
        pub struct Guess {
            value: i32,
        }
        
        impl Guess {
            pub fn new(value: i32) -> Guess {
                if value < 1 || value > 100 {
                    panic!("Guess value must be between 1 and 100, got {}.", value);
                }
        
                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(200);
            }
        }
        
        

2. 运行测试用例

  • 命令:cargo test

    • 这个命令接收两个部分的参数,这两个部分的参数都以 -- 开头,如果第一个参数没有,仍然要用这个符号占位
      • 命令格式:cargo test --<test_case_name> --<param2[=some_value]>
      • 示例命令:cargo test -- --test-threads=1
      • 第一部分:传递给 cargo test 命令本身的参数
        • 这个示例里没有,通常是指定要运行的测试用例的名称或期望测试的模块的名称
      • 第二部分:传递给生成的测试二进制文件的参数
        • 这个示例里是 --test-threads=1
    • 第一部分的参数
      • 通常用于指定测试用例的名称或测试的模块名称
    • 第二部分的参数
      • --test-threads=<number> 控制测试线程的数量
      • --nocapture 控制是否显示成功的测试用例的 print!/println! 输出
        • 带上这个参数时,会显示;反之,不显示
      • --ignored 控制运行的测试用例的忽略类型
        • 带上这个参数时,只会运行被忽略的测试用例 (带 #[ignore])
        • 不带这个参数时,只会运行未被忽略的测试用例 (不带 #[ignore])
  • 注意事项

    • 多个测试用例是多线程并行运行的,一定要注意这些测试用例之间不要互相依赖,也不要有互相冲突的操作 (如文件读写)
    • 测试所在的模块也是测试名称的一部分,所以可以通过模块名来运行一个模块中的所有测试
  • 局部测试

    • 利用第一个参数来指定期望测试的测试用例或期望测试的模块
    • cargo test <test_fn_name> 仅运行 test_fn_name 对应的测试用例
    • cargo test <test_filte> 运行所有名称中包含 test_filte 的测试用例
  • 忽略测试用例

    • #[ignore]
    • 只允许被忽略的测试用例:cargo test -- --ignored

3. 组织测试用例

  • 合理组织测试用例,将能最大程度上提高测试代码的可读性、可测试性和可维护性
  • 单元测试
    • 存放目录:与它们要测试的代码的同一个目录
    • 规范要求:在每一个文件中创建包含测试函数的 tests 模块,并使用 #[cfg(test)] 标注
      • cfg: configuration
  • 集成测试
    • 存放目录:tests 目录,与 src 目录同级
      • 可以创建任意多个测试文件,“Cargo 会将每一个文件当作单独的 crate 来编译”
    • 不需要将 tests/integration_test.rs 中的任何代码标注为 #[cfg(test)]
    • 运行指定的集成测试文件: cargo test --test <file_in_tests>
    • 注意事项
      • tests 目录中的文件不能像 src 中的文件那样共享相同的行为