Rust中的测试函数是用来验证非测试代码是否按照期望的方式运行的。测试函数体通常执行如下三种操作:
设置任何所需的数据或状态
运行需要测试的代码
断言其结果是我们所期望的
使用assert!宏来检查结果
assert!宏由标准库提供,在希望确保测试中一些条件为true时非常有用。需要向assert!宏提供一个求值为布尔值的参数。如果值是true,assert!什么也不做,同时测试会通过。如果值为false,assert!调用panic!宏,这会导致测试失败。assert!宏帮助我们检查代码是否以期望的方式运行。
使用assert_eq!和assert_ne!宏来测试相等
测试功能的一个常用方法是将需要测试代码的值与期望值做比较,并检查是否相等。可以通过向assert!宏传递一个使用==运算符的表达式来做到。不过这个操作实在是太常见了,以至于标准库提供了一个宏来更方便的处理这些操作--assert_eq!和assert_ne!。这两个宏分别比较两个值是相等还是不相等。当断言失败时他们也会打印出这两个值具体是什么,以便于观察测试为什么失败,而assert!只会打印出它从==表达式中得到了false值,而不是导致false的两个值。
测试的组织结构
测试是一个复杂的概念,而且不同的开发者也采用不同的技术和组织。Rust社区倾向于根据测试的两个主要分类来考虑问题:单元测试(unit tests)与集成测试(integration tests)。单元测试倾向于更小而更集中,在隔离的环境中一次测试一个模块,或者是测试私有接口。而集成测试对于你的库来说则完全是外部的。它们与其它外部代码一样,通过相同的方式使用你的代码,只测试公有接口而且每个测试都有可能会测试多个模块。
单元测试
单元测试的目的是在与其它部分隔离的环境中测试每一个单元的代码,以便于快速而准确的某个单元的代码功能是否符合预期。单元测试与他们要测试的代码共同存放在位于src目录下相同的文件中。规范是在每个文件中创建包含测试函数的tests模块,并使用cfg(test)标注模块。
测试模块和#[cfg(test)]
测试模块的#[cfg(test)]注解告诉Rust只在执行cargo test时才编译和运行测试代码,而在运行cargo build时不这么做。这在只希望构建库的时候可以节省编译时间,并且因为它没有包含测试,所以能减少编译产生的文件的大小。与之对应的集成测试因为位于另一个文件夹,所以它们并不需要#[cfg(test)]注解来指定他们不应被包含进编译结果中。
集成测试
在Rust中,集成测试对于你需要测试的库来说完全是外部的。同其它使用库的代码一样使用库文件,也就是说它们只能调用一部分库中的公有api。集成测试的目的是测试库的多个部分是否一起正常工作。一些单独能正确运行的代码单元集成在一起也可能会出现问题,所以集成测试的覆盖率也是很重要的。为了创建集成测试,你需要先创建一个tests目录。
tests目录
为了编写集成测试,需要在项目根目录创建一个tests目录,与src同级。Cargo知道如何去寻找这个目录中的集成测试文件。接着可以随意在这个目录中创建任意多的测试文件,Cargo会将每一个文件当作单独的crate来编译。
让我们来创建一个集成测试。我们在src创建一个lib.rs,并输入如下代码:
创建一个tests目录,新建一个文件tests/integration_tests.rs,结构如下:
输入代码:
与单元测试不同,我们需要在文件顶部添加use calculate::add_two。这是因为每一个tests目录中的测试文件都是完全独立的crate,所以需要在每个文件中导入库。
并不需要将tests/integration_tests.rs中的任何代码标注为#[cfg(test)]。tests文件夹在Cargo中是一个物殊的文件夹,Cargo只会在运行cargo test时编译这个目录中的文件。
深入学习Rust测试