Rust进阶之堪比junit的单元测试和集成测试

     我们来讨论一下怎样测试Rust代码。我们不会讨论的是正确的方式测试Rust代码。有很多学校里学的思想是使用正确和错误的方式写测试。所有的这些方法是用类似的基本工具,我们将会给你展示使用它们的语法。

test属性


     最简单的,在Rust中的测试是一个拥有test属性注解的函数。让我们使用Cargo创建一个名为adder的项目:

     $ cargo new adder
     $ cd adder
    
     当你创建一个项目时,Cargo将会自动产生一个简单的测试。这是src/lib.rs文件的内容:

     #[test]
     fn it_works() {
     }

     注意#[test]。这个属性表明 这是一个测试函数。现在它没有函数体。这已经足够好可以通过!我们可以使用cargo test运行测试:

     $ cargo test
        Compiling adder v0.0.1 (file:///home/you/projects/adder)
          Running target/adder-91b3e234d4ed382a

     running 1 test
     test it_works ... ok

     test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

        Doc-tests adder

     running 0 tests

     test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

     Cargo编译并运行我们的测试。有两种输出的集合:一个是我们写的测试,另一个是文档测试。我们将会在稍后讨论。现在,我们看这一行:

     test it_works ... ok

     注意it_works。它来自于我们的函数名:
     
     fn it_works() {

     我们也得到可以个总结行:

     test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

     为什么我们什么都没做的测试通过了呢?任何没有出现错误(panic!)的测试都会通过,任何出现错误(panic!)的测试都会失败。让我们的测试程序失败:

     #[test]
     fn it_works() {
         assert!(false);
     }

     assert!是Rust提供的一个宏,该宏接受一个参数:如果参数为true,什么都不会发生。如果参数为false,将会产生错误。让我们再次运行test:

     $ cargo test
        Compiling adder v0.0.1 (file:///home/you/projects/adder)
          Running target/adder-91b3e234d4ed382a

     running 1 test
     test it_works ... FAILED

     failures:

     ---- it_works stdout ----
             thread 'it_works' panicked at 'assertion failed: false', /home/steve/tmp/adder/src/lib.rs:3



     failures:
         it_works

     test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured

     thread '<main>' panicked at 'Some tests failed', /home/steve/src/rust/src/libtest/lib.rs:247

     Rust指明我们的测试失败了:
     
     test it_works ... FAILED
    
     并且在总结行也反映出来啦:
     
     test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured

     我们也得到了一个非0的状态码:

     $ echo $?
     101

     如果你想将cargo test集成到其他的工具中是十分有用的。

     我们可以使用另外一个属性倒置我们的测试结果:should_panic:

     #[test]
     #[should_panic]
     fn it_works() {
         assert!(false);
     }

     如果发生错误测试将会成功并且如果没有错误会失败,让我们尝试一下吧:

     $ cargo test
        Compiling adder v0.0.1 (file:///home/you/projects/adder)
          Running target/adder-91b3e234d4ed382a

     running 1 test
     test it_works ... ok

     test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

        Doc-tests adder

     running 0 tests

     test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

     Rust提供了另一个宏assert_eq!,判断两个参数的相等性:

     #[test]
     #[should_panic]
     fn it_works() {
         assert_eq!("Hello", "world");
     }

     这个测试是通过还是失败?因为should_panic属性的存在,它将会通过:

     $ cargo test
        Compiling adder v0.0.1 (file:///home/you/projects/adder)
          Running target/adder-91b3e234d4ed382a

     running 1 test
     test it_works ... ok

     test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

        Doc-tests adder

     running 0 tests

     test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

     should_panic测试是脆弱的,因为它很难保证测试不会因为一个非预期的原因导致失败。为了解决这个问题,一个可选的expected参数可以加在should_panic属性之上。一个安全版本的例子是:

     #[test]
     #[should_panic(expected = "assertion failed")]
     fn it_works() {
         assert_eq!("Hello", "world");
     }

     这都是基础,让我们写一个真实的测试:

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

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

     这是assert_eq!的一个很普通用法:使用已知的参数调用函数并将其与期望的输出比较。

test模块


     我们上面的例子不是习惯用法:它没有tests模块。习惯用法是想下面那样书写我们自己的例子:

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

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

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

     这里有一些改变。第一处是使用cfg属性说明tests模块。这个模块允许我们将所有的测试跟组,如果有需要也可以定义帮助函数,它将不会成为我们的crate的一部分。如果我们试图运行测试用例,cfg属性仅仅编译我们的测试代码。这能节省编译时间,并确保我们的测试用例完全不计入一个普通构建。

     第二处改变是use的声明。因为我们在一个内部模块,我们需要将测试函数带入作用域。如果你有一个大模块将会使人感到厌烦,这是glob特点的一个常规用法。让我们修改是src/lib.rs来使用它:
     
     pub fn add_two(a: i32) -> i32 {
         a + 2
     }

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

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

     注意use行的不同。现在我们运行我们的测试用例:

     $ cargo test
         Updating registry `https://github.com/rust-lang/crates.io-index`
        Compiling adder v0.0.1 (file:///home/you/projects/adder)
          Running target/adder-91b3e234d4ed382a

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

     test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

        Doc-tests adder

     running 0 tests

     test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

     正常工作!

     这种约束使用tests模块来进行单元测试。在这里仅仅测试小功能是有意义的。但是集成测试怎么办?对这个来说我们有tests目录。

tests目录


     系一个集成测试用例,让我们创建一个tests目录,并把tests/lib.rs文件放进去,下面是该文件的内容:

     extern crate adder;

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

     这个看起来跟前面的测试用例类似,但是稍微有些不同。我们现在顶部有一个extern crate adder。这是因为在tests目录下的测试用例是完全分开的crate,因此我们需要导入我们的库。这就是为什么tests是一个合适的地方来写继承测试用例:他们像其他用户一样使用库。

     让我们运行它:

     $ cargo test
        Compiling adder v0.0.1 (file:///home/you/projects/adder)
          Running target/adder-91b3e234d4ed382a

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

     test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

          Running target/lib-c18e7d3494509e74

     running 1 test
     test it_works ... ok

     test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

        Doc-tests adder

     running 0 tests

     test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

     现在有三部分:我们先前的测试用例和我们的新测试用例都运行了。

     这就是tests目录中的所有东西。tests模块在这里并不需要,因为整个狮子那个都聚焦在测试用例上。

     让我们查看一下第三部分:文档测试。

文档测试


     没有什么比带有例子的文档更好了。没有什么比不能正常工作的例子更糟糕,不能工作的原因是文档被写完后代码做了修改。鉴于此,Rust支持西贡运行文档里的例子。这是一个含有例子的src/lib.rs:

     //! The `adder` crate provides functions that add numbers to other numbers.
     //!
     //! # Examples
     //!
     //! ```
     //! assert_eq!(4, adder::add_two(2));
     //! ```

     /// This function adds two to its argument.
     ///
     /// # Examples
     ///
     /// ```
     /// use adder::add_two;
     ///
     /// assert_eq!(4, add_two(2));
     /// ```
     pub fn add_two(a: i32) -> i32 {
         a + 2
     }

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

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

     需要的注意的是模块层文档以//!开头,函数层文档以///开头。在注释中Rust文档支持Markdown,三个‘`’标记代码块。按惯例包含# Example节,详见下面的例子。

     让我们再次运行测试用例:

     $ cargo test
        Compiling adder v0.0.1 (file:///home/steve/tmp/adder)
          Running target/adder-91b3e234d4ed382a
     
     running 1 test
     test tests::it_works ... ok

     test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

          Running target/lib-c18e7d3494509e74

     running 1 test
     test it_works ... ok

     test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
     
        Doc-tests adder

     running 2 tests
     test add_two_0 ... ok
     test _0 ... ok

     test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured

     现在我们已经能使三种测试用例运行!注意文档测试的名字:_0是为模块测试产生的名字,add_two_0是为函数测试产生的名字。当你添加更多例子时这些将会自动增加,比如add_two_1。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值