《Rust权威指南》读书笔记10 - 编写自动化测试

10 - Writing Automated Tests

  测试是保障程序正确性的重要手段。功能上的漏洞不能直接被编译器检测和捕获,必须通过执行一些测试例并比较期望结果得出。手动编写测试用例是一个较为冗长繁琐的过程,Rust提供了一些自动化测试方法,能够标准化、高效化进行功能的自动测试。

编写测试

Rust中的测试是一个测试函数,用于验证目标代码是否能够按照期望的方式运行,并输出期望结果。测试函数函数体一般包含三个部分:

  1. 准备所需的数据或状态
  2. 调用需要测试的代码
  3. 使用**断言 assert **检验运行结果与期望输出是否一致

测试函数

Rust的测试函数是一个标注有test属性的函数。**属性(attribute)**元数据用于修饰Rust代码(derive也是)。只要标注test属性,命令行cargo test就会逐一调用所有的test函数,并生成报告。

创建library类型的工程,模板文件中自带单元测试样例:

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);
    }
}
  • 测试函数由assert族宏构成,每个assert宏都是一个测试样例
    • 上例使用了assert_eq,顾名思义,该宏将判断左右参数是否相等
    • 还有一个是assert_ne,即不相等。还有最通用的assert,判断布尔表达式
    • **assert除了第一个表达式参数后,还可以有panic**宏的format string,以及后续参数。这些参数将被传递给panic,用于错误提示消息输出
  • 当一个测试函数的所有assert通过后,会输出一个测试通过的信息,并将通过数+1
  • 如果一个测试函数的某个assert未通过,则输出错误信息(与普通panic相同),但仍会继续执行测试,并最终输出统计信息

进行测试

为了测试,另行编写一个测试函数如下:

#[test]
fn my_test() {
    assert_eq!(2, 3);
}

运行cargo test,输出如下:

running 2 tests		// progress
test tests::it_works ... ok
test my_test ... FAILED

failures:		// part 1

---- my_test stdout ----
thread 'my_test' panicked at 'assertion failed: `(left == right)`
  left: `2`,
 right: `3`', src\lib.rs:7:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:		// part 2
    my_test
				// summary
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`
NativeCommandExitException: Program "cargo.exe" ended with non-zero exit code: 101.

输出主要由四部分构成(不包含Doc-tests,遇到测试失败):

  • progress 测试运行中,输出测试进度(通用)
  • part 1: 运行失败,各失败的标准输出(即panic输出信息)
  • part 2: 运行失败的测试列表
  • summary: 测试统计摘要(通用)

其他测试语法

可以在test属性的基础上,额外增加should_panic属性,用于检测对象是否按照预期的情况panic

当测试函数运行过程中发生panic,则通过测试,否则不通过

#[test]
#[should_panic]
fn some_panic_test() {
    ...
}

另外,为should_panic属性增加expected参数,可以指定panic输出的消息

#[should_panic(expected="Some expected panic info")]

使用Result编写测试

也可以使用Result编写测试,测试函数的返回值是Result<T, E>,当测试函数返回Err变体,就会被统计到测试失败中。

使用该方法可以在函数中调用?语法糖,编写测试程序更加方便。

控制测试的运行方式

默认情况下,cargo test生成的二进制文件会默认并行运行所有测试,并截获结果信息

有关cargo test测试的更多参数,可以使用--help参数查看

  • test_threads指定并行测试的线程数

  • no-capture不截获正确测试例的标准输出

  • ignored 单独运行被忽略的测试

    • 为测试函数添加ignore属性,可以将这些函数排除在测试外,避免过于消耗资源的测试频繁运行

测试的组织结构

单元测试

单元测试只测试一小段代码的正确性。将项目分成一些小单元,分别对单元进行测试,能够大大降低整合测试的复杂度,优化排错效率。

单元测试模块通常放在每个源代码中,并以#[cfg(test)]标注,该标注使编译器只在运行cargo test时才编译运行该模块

单元测试仍然可以测试私有函数

集成测试

继承测试完全位于代码库之外,通过调用库的接口完成测试,也就是只测试公开的那部分API功能。当然,部分功能也包含于这些API中。当然,集成测试也可能得到一些单元测试测不出来的bug,更加贴近于用户。

集成测试需要建立tests目录,cargo会自动寻找集成测试文件,并对每一个文件处理为独立的包。

在集成测试文件中,使用use引用被测目标,其他测试函数的编写方式与单元测试相同(可以不需要mod和#[cfg(test)]

如果想在集成测试中使用一些通用函数,可以将其放入tests/common/mod.rs中,这可以被rust识别,并且不会被编入普通测试

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值