📕
innohub
  • KEEP LEARNING
  • WebInfo
    • JS 部分运算符操作
    • javascript 中的object array
    • 错误处理以及异常捕获
    • JavaScript Bases
    • LaoZi & Confucius
  • PyInfo
    • Python3 输入与输出
    • Python3OS
    • python 修饰器的使用
    • python3 与mysql数据库连接使用实例
    • Format-specifier
    • CGI初学踩坑
    • Django 项目测试
    • Assert-info
    • 使用ngnix和uWSGI配置Django
    • write|SVN​
    • Matplotlib 基本使用
    • 重读 Python 官方文档
    • Python3 Base
    • python3 多线程
    • Python3 函数解析
    • python3 str 对象的基本操作
    • protocl buffers
    • Iterator-Generator
    • Django base
    • Decorator 2
    • Export to wheel or egg-info
    • 三. 运算、数据及逻辑
  • GoInfo
    • Info
      • Select 语句使用
      • First class function
      • Work Pools
      • StructTag
      • Go struct
      • 常用函数
      • Strings操作
      • Golang Bases
      • 数组以及切片
      • 文件操作
      • Golang 中的指针类型
      • Golang Map 类型
      • Reflection
      • 函数闭包
      • 接口
      • Panic and Recover
      • Go中的错误处理
      • 并发
      • defer usage
      • Method in golang
      • Object-oriented Programming
      • Goalng 包管理机制
  • RustInfo
    • Info
      • 包、crate、模块
      • Rust 中的错误处理
      • 智能指针
      • 泛型 generics
      • 数据布局与内存对齐
      • Functions and methods
      • 无畏并发
      • Actix-web 基本使用
      • Got from guessing game
      • 结构体的使用
      • Rust 中的函数式语言功能
      • 集合类型
      • 枚举以及模式匹配
      • Slice 类型
      • 生命周期
      • 测试
      • Rust 中的所有权
      • trait
      • 不安全 Rust
      • Format_print
      • Rust 通用编程概念
      • Macro
  • OS
    • info
      • 内存屏障 -- Part 1
      • 内存屏障 -- Part 2
      • CPU 上下文切换
      • 文件读写与零拷贝
      • ELF 文件
  • MySql
    • info
      • MySql 架构与历史
      • 02-key
  • kubernetes
    • 第二章 k8s 基本概念
    • 第一章 Kubernetes -- 伟大舵手
  • Redis
    • info
      • Redis 总览
      • 02-underline-ds
  • Shell&CInfo
    • GCC 与make的使用
    • C++ 中 malloc 与 new
    • 位运算符操作
    • Base of C macro
    • C 语言中 extern 关键字的用法
  • Distributed
    • info
      • 分布式理论概述
  • Java
    • info
      • Java 内存模型
  • Prometheus
    • Prometheus -- 不灭之火
Powered by GitBook
On this page
  • 1. 编写测试的基本操作
  • 1.1 test 属性的注释函数
  • 1.2 自定义失败信息
  • 1.3 panic 测试
  • 1.4 用Result<T, E>进行测试
  • 2. 测试控制
  • 2.1 并行或者串行执行测试
  • 2.2 显示函数输出
  • 2.3 运行单个测试
  • 2.4 忽略某些测试
  • 3. 测试的组织结构
  • 3.1 单元测试
  • 3.2 集成测试

Was this helpful?

  1. RustInfo
  2. Info

测试

[TOC]

1. 编写测试的基本操作

Rust中使用函数进行代码的测试,用以验证非测试代码是否按照期望的方式运行,一般具有以下操作:

  • 设置所需要的数据或者状态

  • 运行测试代码

  • 断言结果是否为所期望的

1.1 test 属性的注释函数

Rust 中测试函数是一个带有test属性注释的函数。属性attribute是关于Rust代码片段的元数据,derive就是一个典型的例子。为了将一个函数设置为测试函数,可以在函数名前加上一个#[test].当执行cargo test 时,Rust会构建一个测试程序来调用所有标记了该属性注释的函数。

对于一个成本的测试样例,得到的运行结果:

running 7 tests
test tests::it_works ... ok
test tests::rectangle_large_can_hold_small ... ok
test tests::greeting_not_done ... FAILED
test tests::length_negative ... ok
test tests::rectangle_small_can_not_hold_large ... ok
test tests::return_result ... FAILED
test tests::width_negative ... ok

failures:

---- tests::greeting_not_done stdout ----
thread 'tests::greeting_not_done' panicked at 'Greeting did not contain the name value:Hello, Cargo', src/lib.rs:40:9
note: Run with `RUST_BACKTRACE=1` for a backtrace.

---- tests::return_result stdout ----
Error: "two add two does not equal three"
thread 'tests::return_result' panicked at 'assertion failed: `(left == right)`
  left: `1`,
 right: `0`: the test returned a termination value with a non-zero status code (1) which indicates a failure', src/libtest/lib.rs:337:5


failures:
    tests::greeting_not_done
    tests::return_result

test result: FAILED. 5 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out

输出了所有的失败样例,以及测试结果总结,

通常在一个测试函数中,使用assert!类的宏进行测试的第三项工作,该宏接受一个bool类型的值,通常使用的宏还有assert_eq!, assert_ne!,接受两个类型相同的值,断言相等或者不等。需要注意的是,在一些语言和测试框架中,断言两个值相等的函数的参数叫做 expected 和 actual,而且指定参数的顺序是很关键的。然而在 Rust 中,他们则叫做 left 和 right,同时指定期望的值和被测试代码产生的值的顺序并不重要。这个测试中的断言也可以写成 assert_eq!(add_two(2), 4),这时失败信息会变成 assertion failed:(left == right)` 其中left是5而right是4`。

这两个宏底层实现使用了== != , 同时如果断言失败会使用Debug模式输出错误信息。所以传入的类型必须是实现了PartialEq以及Debug trait 的类型。又因为这两个trait都是派生trait,通常可以在结构体上加#[derive(PartialEq + Debug)]属性注释。

1.2 自定义失败信息

可以向assert!等宏中添加一个可选的默认失败信息,在测试失败时将这些信息打印出来。获取更加详细的测试信息。

例如,对于一个尚未完成的函数进行测试,由于可能Hello可变,只对参数进行测试:

# fn main() {}
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"));
    }
}

如果测试失败,显示的结果只有失败的函数以及行号:

running 1 test
test tests::greeting_contains_name ... FAILED

failures:

---- tests::greeting_contains_name stdout ----
        thread 'tests::greeting_contains_name' panicked at 'assertion failed:
result.contains("Carol")', src/lib.rs:12:8
note: Run with `RUST_BACKTRACE=1` for a backtrace.

failures:
    tests::greeting_contains_name

可以添加更多信息:

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

1.3 panic 测试

对于代码中的panic进行测试时,可以使用should_panic属性注释进行操作:

#[test]
#[should_panic(expected = "The incorrect value of rectangle: 12.1, -5")]
fn width_negative() {
        Rectangle::new(12.1, -5.);
}

impl Rectangle {
        pub fn new(length: f64, width: f64) -> Rectangle {
            if length <= 0. || width <= 0. {
                panic!("The incorrect value of rectangle: {}, {}", length, width);
            }
            Rectangle {
                length,
                width,
            }
        }
}

但是使用这种方式有一个很大的问题,如果是程序的其他部分panic那么该测试样例也是可以通过的,是的测试结果十分含糊不清,所以一般需要加上一个expeted参数,从而获取所期望的panic信息。

1.4 用Result<T, E>进行测试

可以在测试函数返回一个Result<T, E>枚举类型,可以达到同样的效果:

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

这里我们将 it_works 改为返回 Result。同时在函数体中,在成功时返回 Ok(()) 而不是 assert_eq!,而失败时返回带有 String 的 Err。跟之前一样,这个测试可能成功或失败,不过不再通过 panic,可以通过 Result<T, E> 来判断结果。为此不能在对这些函数使用 #[should_panic];而是应该返回 Err!

2. 测试控制

cargo test命令会生成可执行的二进制文件,默认进行的是并行测试,并且截获所有的输出,阻止其输出到console,从而获得更为清晰地测试结果。可以将一部分命令行参数传递给cargo test,而另一部分传递给生成的二进制文件,先列出cargo test的参数,然后是二进制文件参数。

2.1 并行或者串行执行测试

并执行测试时必须保证测试程序间不会生竞争条件,所以测试间不可以相互依赖,或者依赖共享的状态。依赖共享的环境。为了避免测试相互干扰,可以指定单线程运行:

cargo test -- -- test-thread=1

2.2 显示函数输出

cargo test -- -- nocapture

2.3 运行单个测试

cargo test func_name

可以通过指定一个测试函数或者测试模块,运行一部分测试,节省测试时间。这种方式称为filter

2.4 忽略某些测试

对于一些较为耗时的测试,可以添加#[ignore]属性注释进行忽略,从而加快测试进度。

3. 测试的组织结构

本章一开始就提到,测试是一个复杂的概念,而且不同的开发者也采用不同的技术和组织。Rust 社区倾向于根据测试的两个主要分类来考虑问题:单元测试(unit tests)与 集成测试(integration tests)。单元测试倾向于更小而更集中,在隔离的环境中一次测试一个模块,或者是测试私有接口。而集成测试对于你的库来说则完全是外部的。它们与其他外部代码一样,通过相同的方式使用你的代码,只测试公有接口而且每个测试都有可能会测试多个模块。

为了保证你的库能够按照你的预期运行,从独立和整体的角度编写这两类测试都是非常重要的。

3.1 单元测试

单元测试的主要目的是在与其他部分隔离的环境中进行代码的测试。以便于快速而准确地确定某个部分的代码是否符合预期。单元测试与需要测试的代码都存放在src目录下,规范是包含测试函数的tests模块,需要使用#cfg(test)属性标注。

通过使用#cfg(test)保证了测试代码只在cargo test时才编译运行,在cargo build时不需要这么做。可以在构建库时节省时间。同时由于单元测试代码和库文件在同一个文件夹中,所以需要这个属性进行标示,指定他们不可以包含在库文件中。

3.2 集成测试

在 Rust 中,集成测试对于所需要测试的库是完全外部的。同其他使用库的代码一样使用库文件,也就是说只可以调用库中的pubAPI。集成测试的目的是为了保证多个部分是否可以协同工作。集成测试在tests文件夹内进行。

tests目录与scr目录同级,不需要加#cfg(test)属性 tests 文件夹在 Cargo 中是一个特殊的文件夹, Cargo 只会在运行 cargo test 时编译这个目录中的文件。

  Compiling adder v0.1.0 (/home/inno/Learning-notes/RustInfo/projects/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 0.23s
     Running target/debug/deps/adder-70257eb04d3ac84f

running 7 tests
test tests::greeting_not_done ... ok
test tests::it_works ... ok
test tests::rectangle_large_can_hold_small ... ok
test tests::rectangle_small_can_not_hold_large ... ok
test tests::length_negative ... ok
test tests::return_result ... ok
test tests::width_negative ... ok

test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

     Running target/debug/deps/integration_test-b8e9d862b2a5bae4

running 1 test
test it_adds_two ... ok

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

   Doc-tests adder

running 0 tests

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

现在有了三个部分的输出:单元测试、集成测试和文档测试。仍然可以通过指定测试函数的名称作为 cargo test 的参数来运行特定集成测试。也可以使用 cargo test 的 --test 后跟文件的名称来运行某个特定集成测试文件中的所有测试:

Previous生命周期NextRust 中的所有权

Last updated 5 years ago

Was this helpful?