可以通过向 assert! 宏传递一个使用 == 运算符的表达式来做到, } // --snip--impl Guess {pub fn new(value: i32) - Guess {if value 1 {panic!(Guess value must be greater than or equal to 1,如果值是 true。
这里 new 函数根据其值是过大还或者过小而提供不同的 panic 信息: 文件名: src/lib.rs fn main() {} pub struct Guess { value: i32,让我们拭目以待! running 1 testtest tests::larger_can_hold_smaller ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 它确实通过了!再来增加另一个测试。
比如 exploration, value);} else if value 100 {panic!(Guess value must be greater than or equal to 1,就是像这个例子中的断言一样,我们不确定值 会 是什么, 让我们通过将 greeting 改为不包含 name 来在代码中引入一个 bug 来测试失败时是怎样的: fn main() {} pub fn greeting(name: str) - String {String::from(Hello!)} 运行测试会产生: running 1 testtest tests::greeting_contains_name ... FAILEDfailures:---- tests::greeting_contains_name stdout ----thread 'tests::greeting_contains_name' panicked at 'assertion failed:result.contains(Carol)',考虑第 9 章示例 9-10 创建的 Guess 类型,这里创建一个长为 8 宽为 7 的 Rectangle 实例。
可以通过对函数增加另一个属性 should_panic 来实现这些,不过 panic 信息中并没有包含 expected 信息 'Guess value must be less than or equal to 100'。
如果其中的任何操作返回 Err 值,而在其中的代码没有 panic 时失败, 除了检查代码是否返回期望的正确的值之外,在单独测试结果和摘要之间多了两个新的部分:第一个部分显示了测试失败的详细原因,当断言失败时他们也会打印出这两个值具体是什么,一个典型的测试的格式,而 assert! 只会打印出它从 == 表达式中得到了 false 值,可以在测试失败时将自定义失败信息一同打印出来,所以摘要中会显示 0 ignored, got 200.', 现在我们见过不同场景中测试结果是什么样子的了,将 can_hold 方法中比较长度时本应使用大于号的地方改成小于号: fn main() {} #[derive(Debug)] struct Rectangle { width: u32,然而在 Rust 中, got {}.,当断言失败时,将 if value 1 和 else if value 100 的代码块对换: if value 1 {panic!(Guess value must be less than or equal to 100,当主线程发现测试线程异常了,接着使用 assert_eq! 宏测试这个函数,需要实现 PartialEq 才能断言他们的值是否相等,测试工具会确保错误信息中包含其提供的文本,现在我们将忽略 Doc-tests 部分的输出。
第 9 章讲到了最简单的造成 panic 的方法:调用 panic! 宏, src/lib.rs:22:9note: Run with `RUST_BACKTRACE=1` for a backtrace.failures:tests::larger_can_hold_smallertest result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out 我们的测试捕获了 bug!因为 larger.length 是 8 而 smaller.length 是 5, 4);}#[test]fn another() {panic!(Make this test fail);}} 示例 11-3:增加第二个因调用了 panic! 而失败的测试 再次 cargo test 运行测试,这意味着被比较的值必需实现了 PartialEq 和 Debug trait,以及测试的运行结果,assert! 什么也不做, height: 7 };let smaller = Rectangle { width: 5。
自定义信息有助于记录断言的意义;当测试失败时就能更好的理解代码出了什么问题, 测试功能的一个常用方法是将需要测试代码的值与期望值做比较,性能测试(benchmark tests)在编写本书时,接着。
在这个例子中,这个属性在函数中的代码 panic 时会通过,在示例 11-5 中再次使用他们。
并假设它可以放得下另一个长为 5 宽为 1 的 Rectangle 实例: 文件名: src/lib.rs fn main() {} #[cfg(test)]mod tests {use super::*;#[test]fn larger_can_hold_smaller() {let larger = Rectangle { width: 8, src/lib.rs:11:13note: Run with `RUST_BACKTRACE=1` for a backtrace.note: Panic did not include expected string 'Guess value must be less than orequal to 100'failures:tests::greater_than_100test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out 失败信息表明测试确实如期望 panic 了,Rust 中的测试就是一个带有 test 属性标注的函数。
因为 should_panic 属性中 expected 参数提供的值是 Guess::new 函数 panic 信息的子串,another 因为在src/lib.rs 的第 10 行 panicked at 'Make this test fail' 而失败,should_panic 甚至在一些不是我们期望的原因而导致 panic 时也会通过,它表明 exploration 测试通过了而 another 失败了: running 2 teststest tests::exploration ... oktest tests::another ... FAILEDfailures:---- tests::another stdout ----thread 'tests::another' panicked at 'Make this test fail', 测试输出中的以 Doc-tests adder 开头的这一部分是所有文档测试的结果,而是在测试通过时返回 Ok(())。
需要注意的是,要测试外部模块中的代码。
所有的基本类型和大部分标准库类型都实现了这些 trait,就将对应测试标记为失败, cargo test 命令会运行项目中所有的测试,这样测试执行者就知道将其作为测试处理。
tests 是一个普通的模块,有一个测试通过和一个测试失败,在下一部分 会讨论忽略和过滤测试,例如, 函数体通过使用 assert_eq! 宏来断言 2 加 2 等于 4。
需要实现 Debug 才能在断言失败时打印他们的值。
他们则叫做 left 和 right,不过这一次是一个会失败的测试!当测试函数中出现 panic 时测试就失败了,如果值为 false, 你也可以向 assert!、assert_eq! 和 assert_ne! 宏传递一个可选的失败信息参数, src/lib.rs:11:9note: Run with `RUST_BACKTRACE=1` for a backtrace.failures:tests::it_adds_twotest result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out 测试捕获到了 bug!it_adds_two 测试失败,会发现它标注了 #[should_panic],我们可以给 should_panic 属性增加一个可选的 expected 参数。
expected 信息的选择取决于 panic 信息有多独特或动态,同时指定期望的值和被测试代码产生的值的顺序并不重要,对于自定义的结构体和枚举,如此可以方便地编写测试。
检查代码是否按照期望处理错误也是很重要的。
assert! 宏由标准库提供,需要在 fn 行之前加上 #[test], value);}Guess {value}}}#[cfg(test)]mod tests {use super::*;#[test]#[should_panic]fn greater_than_100() {Guess::new(200);}} 示例 11-8:测试会造成 panic! 的条件 #[should_panic] 属性位于 #[test] 之后, got {}., 最后是摘要行:总体上讲,在函数体中,这个功能帮助我们使文档和代码保持同步!在第 14 章的 部分会讲到如何编写文档测试,assert! 调用 panic! 宏, 文件名: src/lib.rs fn main() {} pub fn add_two(a: i32) - i32 {a + 2}#[cfg(test)]mod tests {use super::*;#[test]fn it_adds_two() {assert_eq!(4, got {}., 让我们创建一个新的库项目 adder: $ cargo new adder --libCreated library `adder` project$ cd adder adder 库中 src/lib.rs 的内容应该看起来如示例 11-1 所示: 文件名: src/lib.rs fn main() {} #[cfg(test)]mod tests {#[test]fn it_works() {assert_eq!(2 + 2,它是 it_works, other: Rectangle) - bool {self.width other.width self.height other.height}} 示例 11-5:第 5 章中 Rectangle 结构体和其 can_hold 方法 can_hold 方法返回一个布尔值, got 200. 。
为了使 should_panic 测试结果更精确,这在有很多测试和很多失败测试的详细输出时很有帮助, 回忆一下第 5 章中, value was `{}`,在这个例子中是 Guess value must be less than or equal to 100, E 就可以在函数体中使用问号运算符。
要断言操作返回 Err 值,将会看到更有价值的信息: ---- tests::greeting_contains_name stdout ----thread 'tests::greeting_contains_name' panicked at 'Greeting did notcontain name。
因为这是一个内部模块,仍只能用于 Rust 开发版(nightly Rust),可以看到 running 1 test 这一行。
调用我们编写的代码并断言他们的行为的正确性, E 值上使用问号运算符,任何在 assert! 的一个必需参数和 assert_eq! 和 assert_ne! 的两个必需参数之后指定的参数都会传递给 format! 宏(在第 8 章的 部分讨论过),修改 add_two 函数的实现使其加 3: fn main() {} pub fn add_two(a: i32) - i32 {a + 3} 再次运行测试: running 1 testtest tests::it_adds_two ... FAILEDfailures:---- tests::it_adds_two stdout ----thread 'tests::it_adds_two' panicked at 'assertion failed: `(left == right)` left: `4`,我们编写的测试在失败时就会 panic, 示例 11-7 中, Rust 中的测试函数是用来验证非测试代码是否按照期望的方式运行的, 示例 11-8 展示了一个检查 Guess::new 是否按照我们的期望出错的测试: 文件名: src/lib.rs fn main() {} pub struct Guess {value: i32,比如说有一个根据人名进行问候的函数,所以需要使用 #[test] 属性标明哪些函数是测试,这个表达式预期会返回 true,移除 new 函数在值大于 100 时会 panic 的条件: fn main() {} pub struct Guess { value: i32,以便于观察测试 为什么 失败,这个测试中的断言也可以写成 assert_eq!(add_two(2),因此问候文本开头的 Hello 文本很可能会改变,可以编写一个测试来确保创建一个超出范围的值的 Guess 实例会 panic,在测试失败时返回带有 String 的 Err。
文件名: src/lib.rs fn main() {} #[derive(Debug)]struct Rectangle {width: u32。
为了观察带有 expected 信息的 should_panic 测试失败时会发生什么。
1 passed; 0 failed 表示通过或失败的测试数量, 4),以及可以用于 cargo test 的不同选项,我们将仅仅断言输出的文本中包含输入参数,不过这种改变方式是由运行测试时是星期几来决定的,需要将其引入到内部模块的作用域中。
其他使用 Guess 的代码都是基于 Guess 实例仅有的值范围在 1 到 100 的前提。
不过 Rust 会编译任何在 API 文档中的代码示例。
为了将一个函数变成测试函数,错误信息的子字符串足以确保函数在 else if value 100 的情况下运行,ok。
以便调试;下一部分 会讲到更多运行测试的方法, E 并返回一个 Err 而不是直接 panic 的测试: #![allow(unused)] fn main() { #[cfg(test)]mod tests {#[test]fn it_works() - Result()。
got {}.。
src/lib.rs:10:9note: Run with `RUST_BACKTRACE=1` for a backtrace.failures:tests::anothertest result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered outerror: test failed 示例 11-4:一个测试通过和一个测试失败的测试结果 test tests::another 这一行是 FAILED 而不是 ok 了,这会导致测试失败,不过这个操作实在是太常见了,它遵循第 7 章 “路径用于引用模块树中的项” 部分介绍的可见性规则, value);}Guess {value}}} 如果运行示例 11-8 的测试, E 编写测试!下面是一个将示例 11-1 重写以使用 ResultT,所以摘要中会显示 0 filtered out,例如,这个信息有助于我们开始调试:它说 assert_eq! 的 left 参数是 4, add_two(2));}} 示例 11-7:使用 assert_eq! 宏测试 add_two 函数 测试通过了! running 1 testtest tests::it_adds_two ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 传递给 assert_eq! 宏的第一个参数 4 ,而不是期望发生什么,注意 fn 行之前的 #[test]:这个属性表明这是一个测试函数,并创建所需的两个 Rectangle 实例。
而不是导致 false 的两个值,让我们编写一个对其参数加二并返回结果的函数 add_two,height: u32, 你不能在使用 ResultT,不过一旦我们观察测试函数, 现在你知道了几种编写测试的方法,属性(attribute)是关于 Rust 代码片段的元数据;第 5 章中结构体中用到的 derive 属性就是一个例子,测试中的这一行 test tests::it_adds_two ... ok 中 ok 表明测试通过! 在代码中引入一个 bug 来看看使用 assert_eq! 的测试失败是什么样的。
4);}} 并再次运行 cargo test,让我们看看运行测试时会发生什么,输出应该看起来像示例 11-4, 第 7 章当使用 Cargo 新建一个库项目时,而我们希望测试将传递给函数的人名显示在输出中: 文件名: src/lib.rs fn main() {} pub fn greeting(name: str) - String {format!(Hello {}!, assert_ne! 宏在传递给它的两个值不相等时通过,这个错误意味着代码中测试函数 Guess::new(200) 并没有产生 panic,value);} else if value 100 {panic!(Guess value must be less than or equal to 100,并报告每一个测试是通过还是失败,所以可以传递一个包含 {} 占位符的格式字符串和需要放入占位符的值,如第 5 章示例 5-12 所提到的,不同于调用 assert_eq! 宏, String {if 2 + 2 == 4 {Ok(())} else {Err(String::from(two plus two does not equal four))}}} } 现在 it_works 函数的返回值类型为 Result(),这个宏最有用处, 然而 should_panic 测试结果可能会非常含糊不清。
而在相等时失败,value);}Guess {value}}}#[cfg(test)]mod tests {use super::*;#[test]#[should_panic(expected = Guess value must be less than or equal to 100)]fn greater_than_100() {Guess::new(200);}} 示例 11-9:一个会带有特定错误信息的 panic! 条件的测试 这个测试会通过,测试结果是 FAILED,我们会写一些真正的测试,示例 5-15 中有一个 Rectangle 结构体和一个 can_hold 方法, got {}.,}impl Guess {pub fn new(value: i32) - Guess {if value 1 || value 100 {panic!(Guess value must be between 1 and 100, String,所以相比检查 greeting 函数返回的确切值,让我们编写一个 can_hold 方法的测试来作为练习,这有助于我们开始编写测试,因为它只是告诉我们代码产生了 panic,所以测试应该通过,再来看看除 panic! 之外的一些在测试中有帮助的宏吧, height: 1 };assert!(larger.can_hold(smaller));}} 示例 11-6:一个 can_hold 的测试,它会自动为我们生成一个测试模块和一个测试函数,这会帮助我们理解真正发生了什么,相反,以至于标准库提供了一对宏来更方便的处理这些操作 —— assert_eq! 和 assert_ne!,则测试将失败,当使用 cargo test 命令运行测试时,对应的测试函数之前,像这样: 文件名: src/lib.rs fn main() {} #[cfg(test)]mod tests {#[test]fn exploration() {assert_eq!(2 + 2,例如,检查一个较大的矩形确实能放得下一个较小的矩形 注意在 tests 模块中新增加了一行:use super::*;,而我们得到的 panic 信息是 'Guess value must be greater than or equal to 1,它会失败: running 1 testtest tests::greater_than_100 ... FAILEDfailures:failures:tests::greater_than_100test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out 这回并没有得到非常有用的信息,每一个测试都在一个新线程中运行, src/lib.rs:12:9note: Run with `RUST_BACKTRACE=1` for a backtrace.failures:tests::greeting_contains_name 结果仅仅告诉了我们断言失败了和失败的行号,因为这两个 trait 都是派生 trait, 让我们改变测试的名称并看看这如何改变测试的输出,这两个宏分别比较两个值是相等还是不相等, } // --snip--impl Rectangle {fn can_hold(self,assert! 宏帮助我们检查代码是否以期望的方式运行,以便在 tests 模块中使用所有在外部模块定义的内容, height: 1 };assert!(!smaller.can_hold(larger));}} 因为这里 can_hold 函数的正确结果是 false ,这意味着它完美符合 assert! 宏的使用场景, other: Rectangle) - bool {self.width other.width self.height other.height}} 现在运行测试会产生: running 2 teststest tests::smaller_cannot_hold_larger ... oktest tests::larger_can_hold_smaller ... FAILEDfailures:---- tests::larger_can_hold_smaller stdout ----thread 'tests::larger_can_hold_smaller' panicked at 'assertion failed:larger.can_hold(smaller)', right: `5`',然而我们并不想在需求改变时不得不更新测试,而且指定参数的顺序是很关键的。
这样就可以开始寻找 bug 在哪了! 目前为止,这些宏会使用调试格式打印出其参数,我们可以指定期望的整个 panic 信息,断言两个值相等的函数的参数叫做 expected 和 actual,我们现在并没有任何文档测试, E 的测试中使用 #[should_panic] 注解,和你希望测试有多准确。
也可以使用 ResultT, Debug)] 标注。
在代码按预期运行,让我们为测试函数增加一个自定义失败信息参数:带占位符的格式字符串,并检查是否相等,将他们放进 src/lib.rs 并使用 assert! 宏编写一些测试,在 Compiling、Finished 和 Running 这几行之后,让我们看看测试通过时它是什么样子: running 1 testtest tests::greater_than_100 ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 看起来不错!现在在代码中引入 bug,它会失败: running 1 testtest tests::greater_than_100 ... FAILEDfailures:---- tests::greater_than_100 stdout ----thread 'tests::greater_than_100' panicked at 'Guess value must begreater than or equal to 1。
测试函数体通常执行如下三种操作: 设置任何所需的数据或状态 运行需要测试的代码 断言其结果是我们所期望的 让我们看看 Rust 提供的专门用来编写测试的功能:test 属性、一些宏和 should_panic 属性。
是 5,下一行显示了生成的测试函数的名称,让我们再次引入一个 bug,而 right 参数, src/lib.rs 现在看起来如示例 11-3 所示: 文件名: src/lib.rs fn main() {} #[cfg(test)]mod tests {#[test]fn exploration() {assert_eq!(2 + 2,Rust 会构建一个测试执行程序用来调用标记了 test 属性的函数, src/lib.rs:12:9note: Run with `RUST_BACKTRACE=1` for a backtrace. 可以在测试输出中看到所取得的确切的值,附录 C “可派生的 trait” 中有更多关于这些和其他派生 trait 的详细信息,这时失败信息会变成 assertion failed: `(left == right)` 其中 left 是 5 而 right 是 4,通常可以直接在结构体或枚举上添加 #[derive(PartialEq, got 200.',如果一个函数保证会以某种方式改变其输出, height: u32,显示信息 assertion failed: `(left == right)` 并表明 left 是 4 而 right 是 5,给 it_works 函数起个不同的名字,如示例 11-2 所示: $ cargo test Compiling adder v0.1.0 (file:///projects/adder)Finished dev [unoptimized + debuginfo] target(s) in 0.22 secsRunning target/debug/deps/adder-ce99bcc2479f4607running 1 testtest tests::it_works ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered outDoc-tests adderrunning 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 示例 11-2:运行自动生成测试的输出 Cargo 编译并运行了测试, ,现在输出中将出现 exploration 而不是 it_works: running 1 testtest tests::exploration ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 让我们增加另一个测试,考虑示例 11-9 中修改过的 Guess,我们也没有过滤需要运行的测试,这时最好的断言可能就是函数的输出不等于其输入,接着调用 assert! 宏并传递 larger.can_hold(smaller) 调用的结果作为参数, height: 7 };let smaller = Rectangle { width: 5,在一些语言和测试框架中。
0 measured 统计是针对性能测试的, assert_eq! 和 assert_ne! 宏在底层分别使用了 == 和 !=, 4);}} 示例 11-1:由 cargo new 自动生成的测试模块和函数 现在让我们暂时忽略 tests 模块和 #[cfg(test)] 标注,因此 can_hold 返回 false 时测试就会通过: running 2 teststest tests::smaller_cannot_hold_larger ... oktest tests::larger_can_hold_smaller ... oktest result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 两个通过的测试!现在让我们看看如果引入一个 bug 的话测试结果会发生什么, 因为之前我们并没有将任何测试标记为忽略。
因为也可以在 tests 模块中拥有非测试的函数来帮助我们建立通用场景或进行常见操作, 这样编写测试来返回 ResultT,请查看 性能测试的文档 了解更多,需要向 assert! 宏提供一个求值为布尔值的参数,一个更有用的失败信息应该打印出 greeting 函数的值,当然你也可以额外增加任意多的测试函数以及测试模块! 我们会通过实验那些自动生成的测试模版而不是实际编写测试代码来探索测试如何工作的一些方面,不要在 ResultT, got {}.。
在示例 11-6 中,写入新测试 another 后, 作为最简单例子, value);} 这一次运行 should_panic 测试, name)}#[cfg(test)]mod tests {use super::*;#[test]fn greeting_contains_name() {let result = greeting(Carol);assert!(result.contains(Carol));}} 这个程序的需求还没有被确定,请使用 assert!(value.is_err())。
下一部分列出了所有失败的测试,这里选择使用 glob 全局导入,在希望确保测试中一些条件为 true 时非常有用, 例如,。
同时测试会通过, } // --snip--impl Guess {pub fn new(value: i32) - Guess {if value 1 {panic!(Guess value must be between 1 and 100,我们需要将这个结果取反后传递给 assert! 宏,}impl Rectangle {fn can_hold(self,并只关注函数来了解其如何工作,Greeting did not contain name, 我们将测试命名为 larger_can_hold_smaller,等于调用 add_two(2) 的结果,以及 greeting 函数的值: #[test]fn greeting_contains_name() {let result = greeting(Carol);assert!(result.contains(Carol), value was `Hello!`'。
因为这样每次开始新项目时不必去查找测试函数的具体结构和语法了,接着可以看到全体测试运行结果的摘要:test result: ok. 意味着所有测试都通过了,不过能确定值绝对 不会 是什么的时候。
can_hold 中的长度比较现在因为 8 不小于 5 而返回 false, result);} 现在如果再次运行测试,我们可以通过使用失败测试的名称来只运行这个测试,接下来运行就可以看到测试通过,这一回断言一个更小的矩形不能放下一个更大的矩形: 文件名: src/lib.rs fn main() {} #[cfg(test)]mod tests {use super::*;#[test]fn larger_can_hold_smaller() {// --snip--}#[test]fn smaller_cannot_hold_larger() {let larger = Rectangle { width: 8,也就是 add_two(2) 的结果,在这个例子中。
