04-测试原语
测试原语
Rust内置的测试框架基于一系列主要属性和宏组成的基元。在我们编写任何实际的测试之前,熟悉如何有效地使用它们将非常重要。
属性
Rust 代码中的属性是指元素的注释。元素项是软件包(crate)中的顶层语言结构,例如函数、模块、结构体、枚举和声明的常量,以及在软件包根目录下定义的其他内容。属性通常是编译器内置的,不过也可以由用户通过编译器插件创建。它们指示编译器为其下显示的元素注入额外的代码或含义,如果对应的是模块,那么会对该模块应用上述规则。我们将会在第 7 章详细介绍这些内容。为了简化本小节讨论的主题,我们将会讨论两种属性。
-
[
]:这适用于每个元素,通常显示在它们定义的上方。例如,Rust中的测试函数使用#[test]属性进行注释。它表示该函数将被视为测试工具的一部分。 -
![
]:这适用于每个软件包。注意,与#[ ]相比,其中额外包含一个“!”。它通常位于用户软件包根目录的最顶端部分。
注意
如果要创建程序库项目,那么项目根目录中的文件一般是lib.rs文件,而创建二进制项目时,项目根目录中的文件将是main.rs文件。
还有其他形式的属性,例如在模块中编写测试时使用的#[cfg(test)]。此属性添加在测试模块之上,以提示编译器有条件地编译模块,但仅在测试模式下有效。
属性不仅限于作用在测试代码上,它们在Rust代码中用途广泛。后文将会介绍与之有关的更多内容。
断言宏
在测试中,当给定一个测试用例时,我们尝试在给定的输入区间内断言程序组件的预期行为。语言通常提供被称为断言函数的函数来执行这些断言。Rust为我们提供了通过宏实现的断言函数,帮助我们实现相同的功能。接下来将介绍一些常用的断言函数。
- assert!:这是最简单的断言宏,它是通过布尔值进行断言的。如果值为false,则测试失败,同时会提示产生错误的代码行。此外还可以额外添加格式化字符串,后跟相应数量的变量,用于提供自定义异常消息:
assert!(true);
assert!(a == b, "{} was not equal to {}", a, b);
- assert_eq!:这会接收两个值,如果它们不相等,则会执行失败。它也可以采用自定义异常信息的格式化字符串:
let a = 23;
let b = 87;
assert_eq!(a, b, "{} and {} are not equal", a, b);
- assert_ne!:这与assert_eq!类似,因为它需要接收两个值,但只有在两个值互不相等的情况下才进行断言。
- debug_assert!:这类似于assert!。debug断言宏也可以用在除测试代码之外的代码中。在其他代码中,这主要用于代码运行时,对应该保存的任何契约或不变性进行断言的情况。这些断言仅在调试版本中有效,并且有助于在调试模式下运行代码时捕获断言异常。当代码以优化模式编译时,这些宏调用将被忽略,并被优化为无操作。它还有类似的变体,例如debug_assert_eq!和debug_assert_ne!,它们的工作方式类似assert!宏。
为了比较这些断言宏中的值,Rust需要依赖特征。例如,“assert!(a == b)”中的“==”,实际上会转变成一个方法调用,即a.eq(&b),eq方法来自特征PartialEq。Rust中的大多数内置类型都实现了PartialEq和Eq特征,因此可以对它们进行比较。在第4章中将讨论这些特征的细节,以及PartialEq和Eq之间的区别。
但是,对于用户自定义类型,我们需要实现这些特征。幸运的是,Rust为我们提供了一个名为derive的简便宏,它可以根据名称实现一个或多个特征。可以通过将#[derive(Eq, PartialEq)]注释放在任何用户自定义类型上来使用它,但要注意括号内的特征名称。derive是一个过程宏,它只是简单地为实现它的类型的impl块生成代码,并实现特征方法或任何关联函数。第9章我们将详细讨论这些宏。
接下来,让我们开始编写一些测试。