编写自动化测试(11)

发布于:2024-07-23 ⋅ 阅读:(134) ⋅ 点赞:(0)

1.如何编写测试

1.测试函数剖析

1.创建测试库
cargo new adder --lib
  • cargo new 自动生成的测试模块和函数
pub fn add(left: usize, right: usize) -> usize {
    left + right
}

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

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}
  • cargo test 命令运行项目中所有的测试
$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.57s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

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

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


  • 增加因调用了 panic! 而失败的测试
pub fn add(left: usize, right: usize) -> usize {
    left + right
}

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

    #[test]
    fn exploration() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
    
    #[test]
    fn another() {
        panic!("Make this test fail");
    }
}
  • 调用 cargo test 运行测试
$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.72s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 2 tests
test tests::another ... FAILED
test tests::exploration ... ok

failures:

---- tests::another stdout ----
thread 'tests::another' panicked at src/lib.rs:17:9:
Make this test fail
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`


2.使用 assert! 宏来检查结果

  • assert! 宏由标准库提供,在希望确保测试中一些条件为 true 时非常有用
  • 需要向 assert! 宏提供一个求值为布尔值的参数; 如果值是 trueassert! 什么也不做,同时测试会通过。如果值为 falseassert! 调用 panic! 宏,这会导致测试失败
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn larger_can_hold_smaller() {
        let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };
        assert!(larger.can_hold(&smaller))
    }
    #[test]
    fn smaller_cannot_hold_larger() {
        let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };
        assert!(!smaller.can_hold(&larger))
    }
}

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}
impl Rectangle {
    // fn can_hold(&self, other: &Rectangle) -> bool {
    //     self.width > other.width && self.height > other.height
    // }

    // 引入一个bug,将can_hold()函数方法中比较长度时,本应使用大于号的地方改为小于号
    // larger.length是8,而smaller.length是5,实际应该返回false
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width < other.width && self.height > other.height
    }
}

3.使用assert_eq! 和 assert_ne!宏来测试相等

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

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

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

  • 测试结果
PS C:\Tools\devTools\vscode\code\rust\adder> cargo test
   Compiling adder v0.1.0 (C:\Tools\devTools\vscode\code\rust\adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.16s
     Running unittests src\lib.rs (target\debug\deps\adder-b88936cc1f115a7c.exe)

running 1 test
test tests::it_works ... FAILED

failures:

---- tests::it_works stdout ----
thread 'tests::it_works' panicked at src\lib.rs:11:9:
assertion `left == right` failed
  left: 4
 right: 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::it_works

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `--lib`
  • 比较左右两个值不相等,测试失败 left参数是4,right参数是5

4.自定义失败信息

  • 任何在 assert! 的一个必需参数和 assert_eq!assert_ne! 的两个必需参数之后指定的参数都会传递给 format!
pub fn greeting(name: &str) -> String {
    format!("Hello {}!", name)
}
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn greeting_contains_name() {
        let result = greeting("Carol");
        assert!(
            result.contains("Carol"),
            "Greeting did not contain name, value was `{}`",
            result
        );
    }
}

5.使用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]
    fn greater_than_100() {
        Guess::new(200);
    }
}

6.将Result<T, E>用于测试

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() -> Result<(), String> {
        if 2 + 2 == 4 {
            Ok(())
        } else {
            Err(String::from("two plus two does not equal four"))
        }
        
    }
}

2.控制测试如何运行

1.并行或连续的运行测试

  • 当运行多个测试时,Rust 默认使用线程来并行运行
1.1 精准控制运行测试
  • 测试并行运行或者想要更加精确的控制线程的数量,可以传递 - -test-threads 参数 和 使用线程的数量
cargo test -- --test-threads=1

2.显示函数输出

  • 默认情况下,当测试通过时,Rust 的测试库会截获打印到标准输出的所有内容
  • println! 而测试通过了,我们将不会在终端看到 println! 的输出:只会看到说明测试通过的提示行;如果测试失败了,则会看到所有标准输出和其他错误信息
fn prints_and_returns_10(a: i32) -> i32 {
    println!("I got the value {}", a);
    10
}   
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn this_test_will_pass() {
        let result = prints_and_returns_10(4);
        assert_eq!(result, 10);
    }
    #[test]
    fn this_test_will_fail() {
        let result = prints_and_returns_10(8);
        assert_eq!(result, 5);
    }
}
PS C:\Tools\devTools\vscode\code\rust\显示函数输出> cargo test
   Compiling 显示函数输出 v0.1.0 (C:\Tools\devTools\vscode\code\rust\显示函数输出)
warning: function `prints_and_returns_10` is never used
 --> src\lib.rs:1:4
  |
1 | fn prints_and_returns_10(a: i32) -> i32 {
  |    ^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(dead_code)]` on by default

warning: `显示函数输出` (lib) generated 1 warning
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.67s
     Running unittests src\lib.rs (target\debug\deps\显示函数输出-68b68f71791c7651.exe)

running 2 tests
test tests::this_test_will_pass ... ok
test tests::this_test_will_fail ... FAILED

failures:

---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'tests::this_test_will_fail' panicked at src\lib.rs:17:9:
assertion `left == right` failed
  left: 10
 right: 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::this_test_will_fail

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
1.显示成功测试的输出
cargo test -- --show-output

3.通过指定名称来运行部分测试

1.运行单个测试
  • 可以向 cargo test funName 传递任意测试的名称来只运行某个测试
2.过滤运行多个测试
  • 可以指定部分测试的名称,任何名称匹配这个名称的测试会被运行
  • cargo test add 匹配名称带add字段方法的所有测试

4.除非特别指定否则忽略某些测试

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

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn add_two_and_two() {
        assert_eq!(4, add_two(2));
    }
    #[test]
    fn add_three_and_two() {
        assert_eq!(5, add_two(3));
    }
    #[test]
    #[ignore]
    fn one_hundred() {
        assert_eq!(102, add_two(100));
    }
}

  • #[test] 之后添加 #[ignore] 行,进行cargo test ,会忽略添加 #[ignore]
  • 如果只要运行被忽略的测试,可以使用 cargo test – --ignored
  • 如果测试方法包含注解 #[ignore] ,想要运行全部测试,可以运行 cargo test – --include-ignored

3.测试的组织结构

1.单元测试

  • 单元测试的目的是在与其他部分隔离的环境中测试每一个单元的代码,以便于快速而准确地验证某个单元的代码功能是否符合预期
  • 单元测试与它们要测试的代码共同存放在位于 src 目录下相同的文件中
  • 规范是在每个文件中创建包含测试函数的 tests 模块,并使用 cfg(test) 标注模块
1.1测试模块和 #[cfg(test)]
  • cfg 属性代表配置(configuration) ,它告诉 Rust,接下来的项,只有在给定特定配置选项时,才会被包含
  • 配置选项是 test,即 Rust 所提供的用于编译和运行测试的配置选项
  • 通过使用 cfg 属性,Cargo 只会在我们主动使用 cargo test 运行测试时才编译测试代码
1.2测试私有函数

2.集成测试

1.tests目录
  • 为了编写集成测试,需要在项目根目录创建一个 tests 目录,与 src 同级
adder
├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
    └── integration_test.rs

adder/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));
//     }
// }



adder/tests/integration_test.rs

use adder::add_two;
#[test]
fn it_adds_two() {
    assert_eq!(4, add_two(2));
}
  • 通过 cargo test --test 文件名称 ,运行某个特定集成测试文件中的所有测试
2.集成测试中的子模块
  • 项目目录结构
├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
    ├── common
    │   └── mod.rs
    └── integration_test.rs

tests/common/mod.rs

pub fn setup() {
    // setup code specific to your library's tests would go here
}

tests/integration_test.rs

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

3.二进制crate的集成测试

  • 项目是二进制 crate 并且只包含 src/main.rs 而没有 src/lib.rs,这样就不可能在 tests 目录创建集成测试并使用 extern crate 导入 src/main.rs 中定义的函数
  • 只有库 crate 才会向其他 crate 暴露了可供调用和使用的函数;二进制 crate 只意在单独运行
  • 许多 Rust 二进制项目使用一个简单的 src/main.rs 调用 src/lib.rs 中的逻辑的原因之一
  • 集成测试 就可以 通过 extern crate 测试库 crate 中的主要功能

网站公告

今日签到

点亮在社区的每一天
去签到