開發(fā)環(huán)境
- Windows 10
- Rust 1.71.1
?
- VS Code 1.81.1
?項(xiàng)目工程
這里繼續(xù)沿用上次工程rust-demo
編寫自動(dòng)化測試
Edsger W. Dijkstra在他1972年的文章《謙遜的程序員》中說,“程序測試可以是一種非常有效的方法來顯示錯(cuò)誤的存在,但它對于顯示它們的不存在是完全不夠的?!边@并不意味著我們不應(yīng)該盡可能多地嘗試測試!?
我們程序的正確性是指我們的代碼在多大程度上做了我們想要它做的事情。Rust的設(shè)計(jì)高度關(guān)注程序的正確性,但正確性很復(fù)雜,不容易證明。Rust的類型系統(tǒng)承擔(dān)了這個(gè)負(fù)擔(dān)的很大一部分,但是類型系統(tǒng)不能捕獲所有的東西。因此,Rust包含了對編寫自動(dòng)化軟件測試的支持。
假設(shè)我們編寫了一個(gè)函數(shù)add_two,它將傳遞給它的任何數(shù)字加2。這個(gè)函數(shù)的簽名接受一個(gè)整數(shù)作為參數(shù),并返回一個(gè)整數(shù)作為結(jié)果。當(dāng)我們實(shí)現(xiàn)和編譯這個(gè)函數(shù)時(shí),Rust會進(jìn)行所有的類型檢查和借用檢查,就像你到目前為止所學(xué)的那樣,以確保我們不會向這個(gè)函數(shù)傳遞一個(gè)String值或一個(gè)無效的引用。但是Rust不能檢查這個(gè)函數(shù)是否能準(zhǔn)確地達(dá)到我們的目的,即返回參數(shù)加2,而不是參數(shù)加10或參數(shù)減50!這就是測試的由來。
我們可以編寫一些測試來斷言,例如,當(dāng)我們將3傳遞給add_two函數(shù)時(shí),返回值是5。每當(dāng)我們對代碼進(jìn)行更改時(shí),我們都可以運(yùn)行這些測試,以確保任何現(xiàn)有的正確行為都沒有改變。
測試是一項(xiàng)復(fù)雜的技能:雖然我們無法在一章中涵蓋如何編寫好的測試的每個(gè)細(xì)節(jié),但我們將討論Rust測試工具的機(jī)制。我們將討論編寫測試時(shí)可用的注釋和宏,為運(yùn)行測試提供的默認(rèn)行為和選項(xiàng),以及如何將測試組織成單元測試和集成測試。
如何編寫測試
測試是Rust函數(shù),它驗(yàn)證非測試代碼是否以預(yù)期的方式運(yùn)行。測試函數(shù)的主體通常執(zhí)行這三個(gè)動(dòng)作:
- 設(shè)置任何需要的數(shù)據(jù)或狀態(tài)。
- 運(yùn)行您想要測試的代碼。
- 斷言結(jié)果是你所期望的。
讓我們來看看Rust專門為編寫執(zhí)行這些操作的測試提供的特性,包括test屬性、一些宏和should_panic屬性。
測試函數(shù)的剖析
最簡單地說,Rust中的測試是一個(gè)用test屬性注釋的函數(shù)。屬性是關(guān)于Rust代碼片段的元數(shù)據(jù);一個(gè)例子是我們在第5章中對結(jié)構(gòu)使用的derive屬性。要將函數(shù)更改為測試函數(shù),請?jiān)?strong>fn之前的行中添加#[test]。當(dāng)您使用cargo test命令運(yùn)行您的測試時(shí),Rust會構(gòu)建一個(gè)測試運(yùn)行二進(jìn)制文件,運(yùn)行帶注釋的函數(shù)并報(bào)告每個(gè)測試函數(shù)是通過還是失敗。
每當(dāng)我們用Cargo創(chuàng)建一個(gè)新的庫項(xiàng)目時(shí),就會自動(dòng)為我們生成一個(gè)包含測試功能的測試模塊。這個(gè)模塊為您提供了一個(gè)編寫測試的模板,這樣您就不必在每次開始一個(gè)新項(xiàng)目時(shí)都去查找精確的結(jié)構(gòu)和語法。您可以添加任意多的額外測試函數(shù)和測試模塊!??
在實(shí)際測試任何代碼之前,我們將通過模板測試來探索測試工作的某些方面。然后,我們將編寫一些真實(shí)世界的測試,調(diào)用我們編寫的一些代碼,并斷言其行為是正確的。?
讓我們創(chuàng)建一個(gè)名為adder的新庫項(xiàng)目,它將添加兩個(gè)數(shù):
$ cargo new adder --lib
Created library `adder` project
$ cd adder
?adder庫中src/lib.rs文件的內(nèi)容應(yīng)該如示例11-1所示。
文件名:src/lib.rs
#[cfg(test)]
mod tests {
#[test] // 測試
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
示例11-1:由cargo new自動(dòng)生成的測試模塊和函數(shù)
現(xiàn)在,讓我們忽略上面兩行,把注意力集中在函數(shù)上。注意#[test]注釋:這個(gè)屬性表明這是一個(gè)測試函數(shù),所以測試運(yùn)行人員知道將這個(gè)函數(shù)視為一個(gè)測試。在tests模塊中,我們還可能有非測試函數(shù)來幫助設(shè)置常見的場景或執(zhí)行常見的操作,所以我們總是需要指出哪些函數(shù)是測試。
示例函數(shù)體使用了assert_eq!宏來斷言包含2和2相加result的結(jié)果等于4。這個(gè)斷言作為一個(gè)典型測試格式的例子。讓我們運(yùn)行它來看看這個(gè)測試是否通過。?
cargo test命令運(yùn)行我們項(xiàng)目中的所有測試,如示例11-2所示。
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.57s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
示例11-2:運(yùn)行自動(dòng)生成的測試的輸出?
?Cargo編譯并運(yùn)行了測試。我們看到runing1 test的行。下一行顯示了生成的測試函數(shù)的名稱,名為it_works,運(yùn)行該測試的結(jié)果是ok的??傮w總結(jié)test result: ok。意味著所有的測試都通過了,而寫著1 passed; 0 failed總計(jì)測試通過或失敗的次數(shù)。?
0 measured測量統(tǒng)計(jì)值用于測量性能的基準(zhǔn)測試。在撰寫本文時(shí),基準(zhǔn)測試只在夜間Rust中可用。
從Doc-tests adder開始的測試輸出的下一部分是任何文檔測試的結(jié)果。我們還沒有任何文檔測試,但Rust可以編譯任何出現(xiàn)在我們API文檔中的代碼示例。這個(gè)特性有助于保持您的文檔和代碼同步!現(xiàn)在,我們將忽略Doc-tests輸出。
讓我們開始根據(jù)自己的需要定制測試。首先將it_works函數(shù)的名稱改為不同的名稱,例如exploration,如下所示:
文件名:src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn exploration() {
assert_eq!(2 + 2, 4);
}
}
然后再次運(yùn)行cargo test?,F(xiàn)在輸出顯示的是exploration而不是it_works:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.59s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::exploration ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
?現(xiàn)在我們將添加另一個(gè)測試,但這次我們將做一個(gè)失敗的測試!當(dāng)測試函數(shù)出現(xiàn)問題時(shí),測試就會失敗。每個(gè)測試都在一個(gè)新線程中運(yùn)行,當(dāng)主線程發(fā)現(xiàn)一個(gè)測試線程已經(jīng)死亡時(shí),該測試就會被標(biāo)記為失敗。在第9章中,我們談到了恐慌的最簡單的方法是如何打電話給panic!宏觀。輸入新的測試作為一個(gè)名為another的函數(shù),這樣你的src/lib.rs文件看起來如示例11-3所示。
文件名:src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn exploration() {
assert_eq!(2 + 2, 4);
}
#[test]
fn another() {
panic!("Make this test fail");
}
}
?示例11-3:添加第二個(gè)測試,該測試將失敗,因?yàn)槲覀兎Q之為panic!宏指令
使用cargo test再次運(yùn)行測試。輸出應(yīng)該如示例11-4所示,這表明我們的exploration測試通過了,而another測試失敗了。?
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.72s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 2 tests
test tests::another ... FAILED
test tests::exploration ... ok
failures:
---- tests::another stdout ----
thread 'tests::another' panicked at 'Make this test fail', src/lib.rs:10:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::another
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`
示例11-4:當(dāng)一個(gè)測試通過而另一個(gè)測試失敗時(shí)的測試結(jié)果
?行test?tests::another顯示FAILED,而不是ok。在單獨(dú)的結(jié)果和總結(jié)之間出現(xiàn)兩個(gè)新的部分:第一部分顯示每個(gè)測試失敗的詳細(xì)原因。在這種情況下,我們獲得another失敗的詳細(xì)信息,因?yàn)樗趕rc/lib.rs文件的第10行panicked at 'Make this test fail'時(shí)死機(jī)。?
總結(jié)行顯示在最后:總的來說,我們的測試結(jié)果是FAILED的。我們有一個(gè)測試通過,一個(gè)測試失敗。?
現(xiàn)在您已經(jīng)看到了不同場景下的測試結(jié)果,讓我們來看看除了panic!之外的一些宏在測試中很有用。?
用assert!宏檢查結(jié)果
assert!當(dāng)您希望確保測試中的某個(gè)條件評估為真時(shí),標(biāo)準(zhǔn)庫提供的宏非常有用。我們給出assert!計(jì)算結(jié)果為布爾值的參數(shù)。如果該值為true,則什么都不會發(fā)生,測試通過。如果值為false,則assert!調(diào)用panic!導(dǎo)致測試失敗。使用assert!幫助我們檢查我們的代碼是否按照我們想要的方式運(yùn)行。
在第五章的示例5-15中,我們使用了一個(gè)Rectangular結(jié)構(gòu)和一個(gè)can_hold方法,它們在示例11-5中重復(fù)出現(xiàn)。讓我們將這段代碼放到src/lib.rs文件中,然后使用assert!為它編寫一些測試。
?文件名:src/lib.rs
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
示例11-5:使用第5章中的Rectangular結(jié)構(gòu)及其can_hold方法
can_hold方法返回一個(gè)布爾值,這意味著它是assert!的完美用例。在示例11-6中,我們編寫了一個(gè)測試,通過創(chuàng)建一個(gè)寬度為8、高度為7的Rectangular實(shí)例,并斷言它可以容納另一個(gè)寬度為5、高度為1的Rectangular實(shí)例,來練習(xí)can_hold方法。?
文件名:src/lib.rs
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn larger_can_hold_smaller() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
assert!(larger.can_hold(&smaller));
}
}
?示例11-6:對can_hold的測試,檢查一個(gè)較大的矩形是否可以容納一個(gè)較小的矩形
注意,我們在tests模塊中添加了新的一行:use super::*;。tests模塊是一個(gè)常規(guī)的模塊,它遵循我們在第7章節(jié)中提到的常見的可見性規(guī)則。因?yàn)?strong>tests模塊是一個(gè)內(nèi)部模塊,我們需要將外部模塊中的測試代碼放到內(nèi)部模塊的范圍內(nèi)。我們在這里使用了一個(gè)glob,所以我們在外部模塊中定義的任何東西都可以用于這個(gè)tests模塊。?
我們將我們的測試命名為llarger_can_hold_smaller,并且創(chuàng)建了我們需要的兩個(gè)矩形實(shí)例。然后我們調(diào)用了assert!宏,并將調(diào)用larger.can_hold(&smaller)的結(jié)果傳遞給它。這個(gè)表達(dá)式應(yīng)該返回true,所以我們的測試應(yīng)該通過。讓我們來了解一下!
$ cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
running 1 test
test tests::larger_can_hold_smaller ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests rectangle
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
?確實(shí)測試通過了!讓我們添加另一個(gè)測試,這次斷言較小的矩形不能容納較大的矩形:
文件名:src/lib.rs
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn larger_can_hold_smaller() {
// --snip--
}
#[test]
fn smaller_cannot_hold_larger() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
assert!(!smaller.can_hold(&larger));
}
}
?因?yàn)樵谶@種情況下can_hold函數(shù)的正確結(jié)果是false,所以我們需要在將結(jié)果傳遞給assert!之前對其求反。因此,如果can_hold返回false,我們的測試將通過:
$ cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
running 2 tests
test tests::larger_can_hold_smaller ... ok
test tests::smaller_cannot_hold_larger ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests rectangle
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
兩項(xiàng)測試通過!現(xiàn)在讓我們看看當(dāng)我們在代碼中引入一個(gè)bug時(shí),測試結(jié)果會發(fā)生什么。我們將更改can_hold方法的實(shí)現(xiàn),在比較寬度時(shí)用小于號替換大于號:?
// --snip--
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width < other.width && self.height > other.height
}
}
?運(yùn)行測試現(xiàn)在會產(chǎn)生以下結(jié)果:
$ cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
running 2 tests
test tests::larger_can_hold_smaller ... FAILED
test tests::smaller_cannot_hold_larger ... ok
failures:
---- tests::larger_can_hold_smaller stdout ----
thread 'tests::larger_can_hold_smaller' panicked at 'assertion failed: larger.can_hold(&smaller)', src/lib.rs:28:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::larger_can_hold_smaller
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`
我們的測試發(fā)現(xiàn)了漏洞!因?yàn)?strong>larger.width是8,smaller.width是5,所以can_hold中的寬度比較現(xiàn)在返回false: 8不小于5。
用宏assert_eq!和assert_ne!測試相等性
驗(yàn)證功能的一種常見方法是測試被測代碼的結(jié)果與您期望代碼返回的值之間是否相等。您可以使用assert!來做到這一點(diǎn),并使用==運(yùn)算符向其傳遞一個(gè)表達(dá)式。然而,這是一個(gè)非常常見的測試,標(biāo)準(zhǔn)庫提供了一對宏——assert_eq!和assert_eq!—為了更方便地執(zhí)行該測試。這些宏分別比較相等或不相等的兩個(gè)參數(shù)。如果斷言失敗,它們還會打印這兩個(gè)值,這更容易看出測試失敗的原因;反之,assert!只指示它為==表達(dá)式獲得了一個(gè)false,而不打印導(dǎo)致false的值。
在示例11-7中,我們編寫了一個(gè)名為add_two的函數(shù),將2加到它的參數(shù)中,然后我們使用assert_eq!測試這個(gè)函數(shù)。?
文件名:src/lib.rs
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_adds_two() {
assert_eq!(4, add_two(2));
}
}
?示例11-7:使用assert_eq!測試函數(shù)add_two
讓我們檢查它是否通過!?
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.58s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::it_adds_two ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
?我們將4作為參數(shù)傳遞給assert_eq!,等于調(diào)用add_two(2)的結(jié)果。這個(gè)測試的代碼行是test tests::it_adds_two...ok,ok文本表示我們的測試通過了!
讓我們在代碼中引入一個(gè)bug,看看assert_eq!是什么看起來當(dāng)它失敗時(shí)。將add_two函數(shù)的實(shí)現(xiàn)改為添加3:
pub fn add_two(a: i32) -> i32 {
a + 3
}
再次運(yùn)行測試:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::it_adds_two ... FAILED
failures:
---- tests::it_adds_two stdout ----
thread 'tests::it_adds_two' panicked at 'assertion failed: `(left == right)`
left: `4`,
right: `5`', src/lib.rs:11:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::it_adds_two
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
我們的測試發(fā)現(xiàn)了bug!it_adds_two測試失敗,消息告訴我們失敗的斷言是assertion failed: `(left == right)`以及left 和right值是什么。這條消息幫助我們開始調(diào)試:left的參數(shù)是4,但是right的參數(shù)是5,這里我們有add_two(2)。你可以想象,當(dāng)我們有很多測試正在進(jìn)行時(shí),這將特別有幫助。
注意,在一些語言和測試框架中,等式斷言函數(shù)的參數(shù)被稱為expected和actual,我們指定參數(shù)的順序很重要。然而,在Rust中,它們被稱為left和right,我們指定我們期望的值和代碼產(chǎn)生的值的順序并不重要。我們可以把這個(gè)測試中的斷言寫成assert_eq!(add_two(2), 4),這將導(dǎo)致顯示斷言失敗的相同assertion failed::`(left == right)`。
assert_ne!如果我們給它的兩個(gè)值不相等,將通過,如果相等,將失敗。當(dāng)我們不確定一個(gè)值是什么,但是我們知道這個(gè)值絕對不應(yīng)該是什么時(shí),這個(gè)宏是最有用的。例如,如果我們正在測試一個(gè)函數(shù),它肯定會以某種方式改變它的輸入,但是改變輸入的方式取決于我們在一周中的哪一天運(yùn)行測試,那么最好的斷言可能是函數(shù)的輸出不等于輸入。
表面之下,是assert_eq!和assert_ne!宏使用運(yùn)算符==和!=,分別為。當(dāng)斷言失敗時(shí),這些宏使用調(diào)試格式打印它們的參數(shù),這意味著被比較的值必須實(shí)現(xiàn)PartialEq和Debug特征。所有基本類型和大多數(shù)標(biāo)準(zhǔn)庫類型都實(shí)現(xiàn)了這些特征。對于您自己定義的結(jié)構(gòu)和枚舉,您需要實(shí)現(xiàn)PartialEq來斷言這些類型的相等性。當(dāng)斷言失敗時(shí),您還需要實(shí)現(xiàn)Debug來打印值。因?yàn)檫@兩個(gè)特征都是可派生的特征,正如第5章示例5-12中提到的,這通常就像在你的結(jié)構(gòu)或枚舉定義中添加#[derive(PartialEq,Debug)]注釋一樣簡單。請參閱附錄C,“可衍生特征”,了解有關(guān)這些和其他可衍生特征的更多詳細(xì)信息。
添加自定義失敗消息
您還可以添加一個(gè)自定義消息,作為assert!的可選參數(shù),與失敗消息一起打印,assert_eq!,和assert_ne!。所需參數(shù)之后指定的任何參數(shù)都將傳遞給format!宏(在第8章的“用+運(yùn)算符串聯(lián)or格式!宏”部分),因此您可以傳遞一個(gè)包含{}占位符和要放入這些占位符的值的格式字符串。自定義消息對于記錄斷言的含義非常有用;當(dāng)一個(gè)測試失敗時(shí),您會對代碼的問題有更好的了解。
例如,假設(shè)我們有一個(gè)用名字問候別人的函數(shù),我們想測試我們傳遞給函數(shù)的名字是否出現(xiàn)在輸出中:
文件名:src/lib.rs?
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"));
}
}
?對這個(gè)程序的要求還沒有達(dá)成一致,我們很確定開頭的Hello文本會改變。我們決定,當(dāng)需求改變時(shí),我們不需要更新測試,所以我們不檢查greeting函數(shù)返回的值是否完全相等,而是斷言輸出包含輸入?yún)?shù)的文本。
現(xiàn)在讓我們通過將greeting改為排除name來引入一個(gè)bug,看看默認(rèn)測試失敗是什么樣子的:
pub fn greeting(name: &str) -> String {
String::from("Hello!")
}
?運(yùn)行該測試會產(chǎn)生以下結(jié)果:
$ cargo test
Compiling greeter v0.1.0 (file:///projects/greeter)
Finished test [unoptimized + debuginfo] target(s) in 0.91s
Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a)
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:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::greeting_contains_name
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
這個(gè)結(jié)果只是表明斷言失敗了,以及斷言在哪一行。更有用的失敗消息是打印greeting函數(shù)的值。讓我們添加一個(gè)定制的失敗消息,該消息由一個(gè)格式字符串組成,該格式字符串帶有一個(gè)占位符,占位符中填充了我們從greeting函數(shù)中獲得的實(shí)際值:
#[test]
fn greeting_contains_name() {
let result = greeting("Carol");
assert!(
result.contains("Carol"),
"Greeting did not contain name, value was `{}`",
result
);
}
現(xiàn)在,當(dāng)我們運(yùn)行測試時(shí),我們將得到一個(gè)更具信息性的錯(cuò)誤消息:
$ cargo test
Compiling greeter v0.1.0 (file:///projects/greeter)
Finished test [unoptimized + debuginfo] target(s) in 0.93s
Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a)
running 1 test
test tests::greeting_contains_name ... FAILED
failures:
---- tests::greeting_contains_name stdout ----
thread 'tests::greeting_contains_name' panicked at 'Greeting did not contain name, value was `Hello!`', src/lib.rs:12:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::greeting_contains_name
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
?我們可以在測試輸出中看到我們實(shí)際得到的值,這將幫助我們調(diào)試發(fā)生了什么,而不是我們預(yù)期會發(fā)生什么。
使用should_panic檢查panic
除了檢查返回值,檢查我們的代碼是否如我們所期望的那樣處理錯(cuò)誤情況也很重要。例如,考慮我們在第9章示例9-13中創(chuàng)建的猜測類型。其他使用Guess的代碼依賴于Guess實(shí)例只包含1到100之間的值的保證。我們可以編寫一個(gè)測試,確保試圖創(chuàng)建一個(gè)值在該范圍之外的Guess實(shí)例時(shí)會出錯(cuò)。
我們通過向測試函數(shù)添加屬性should_panic來實(shí)現(xiàn)這一點(diǎn)。如果函數(shù)內(nèi)部的代碼出現(xiàn)混亂,則測試通過;如果函數(shù)內(nèi)部的代碼沒有死機(jī),測試就會失敗。
示例11-8顯示了一個(gè)測試,它檢查Guess::new的錯(cuò)誤條件是否在我們期望的時(shí)候發(fā)生。
文件名:src/lib.rs
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {}.", value);
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn greater_than_100() {
Guess::new(200);
}
}
?示例11-8:測試一個(gè)條件會導(dǎo)致一個(gè)panic!
我們將#[should_panic]屬性放在#[test]屬性之后,它所應(yīng)用的測試函數(shù)之前。讓我們看看測試通過后的結(jié)果:?
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished test [unoptimized + debuginfo] target(s) in 0.58s
Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
running 1 test
test tests::greater_than_100 - should panic ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests guessing_game
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
看起來不錯(cuò)!現(xiàn)在,讓我們在代碼中引入一個(gè)錯(cuò)誤,刪除如果值大于100,new將會死機(jī)的條件:?
// --snip--
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!("Guess value must be between 1 and 100, got {}.", value);
}
Guess { value }
}
}
當(dāng)我們運(yùn)行示例11-8中的測試時(shí),它會失敗:
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished test [unoptimized + debuginfo] target(s) in 0.62s
Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
running 1 test
test tests::greater_than_100 - should panic ... FAILED
failures:
---- tests::greater_than_100 stdout ----
note: test did not panic as expected
failures:
tests::greater_than_100
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
?在這種情況下,我們沒有得到非常有用的消息,但是當(dāng)我們查看測試函數(shù)時(shí),我們看到它被注釋為#[should_panic]。我們得到的失敗意味著測試函數(shù)中的代碼沒有導(dǎo)致死機(jī)。
使用should_panic的測試可能不精確。should_panic測試將會通過,即使測試因不同于我們預(yù)期的原因而死機(jī)。為了使should_panic測試更加精確,我們可以向should_panic屬性添加一個(gè)可選的預(yù)期參數(shù)。測試工具將確保失敗消息包含所提供的文本。例如,考慮示例11-9中Guess的修改代碼,其中new根據(jù)值是太小還是太大而出現(xiàn)不同的消息。
文件名:src/lib.rs
// --snip--
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!(
"Guess value must be greater than or equal to 1, got {}.",
value
);
} else if value > 100 {
panic!(
"Guess value must be less than or equal to 100, got {}.",
value
);
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "less than or equal to 100")]
fn greater_than_100() {
Guess::new(200);
}
}
?示例11-9:測試panic!帶有包含指定子字符串的緊急消息
這個(gè)測試將會通過,因?yàn)槲覀冊?strong>should_panic屬性的expected參數(shù)中輸入的值是Guess::new函數(shù)出錯(cuò)的消息的子字符串。我們可以指定我們期望的整個(gè)緊急消息,在本例中,Guess value must be less than or equal to 100, got 200.。您選擇指定的內(nèi)容取決于恐慌消息中有多少是獨(dú)特的或動(dòng)態(tài)的,以及您希望測試有多精確。在這種情況下,緊急消息的子字符串足以確保測試函數(shù)中的代碼執(zhí)行else if value > 100的情況。?
為了查看當(dāng)帶有expected消息的should_panic測試失敗時(shí)會發(fā)生什么,讓我們通過交換if value < 1和else if value > 100塊的主體,再次在代碼中引入一個(gè)bug:
if value < 1 {
panic!(
"Guess value must be less than or equal to 100, got {}.",
value
);
} else if value > 100 {
panic!(
"Guess value must be greater than or equal to 1, got {}.",
value
);
}
這一次,當(dāng)我們運(yùn)行should_panic測試時(shí),它將失敗:
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
running 1 test
test tests::greater_than_100 - should panic ... FAILED
failures:
---- tests::greater_than_100 stdout ----
thread 'tests::greater_than_100' panicked at 'Guess value must be greater than or equal to 1, got 200.', src/lib.rs:13:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: panic did not contain expected string
panic message: `"Guess value must be greater than or equal to 1, got 200."`,
expected substring: `"less than or equal to 100"`
failures:
tests::greater_than_100
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
失敗消息表明該測試確實(shí)如我們預(yù)期的Result<T,E >那樣死機(jī),但是死機(jī)消息不包括預(yù)期的字符串'Guess value must be less than or equal to 100'。在這種情況下,我們得到的緊急消息是Guess value must be greater than or equal to 1, got 200。現(xiàn)在我們可以開始找出我們的錯(cuò)誤在哪里了!
在測試中使用Result< T,E >
到目前為止,我們的測試失敗時(shí)都會驚慌失措。我們也可以編寫使用Result<T,E >的測試!下面是示例11-1中的測試,重寫后使用Result<T,E >并返回一個(gè)Err而不是死機(jī):
#[cfg(test)]
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函數(shù)現(xiàn)在有了Result <(),String >返回類型。在函數(shù)體中,而不是調(diào)用assert_eq!,當(dāng)測試通過時(shí),我們返回Ok(()),當(dāng)測試失敗時(shí),返回一個(gè)包含String的Err。
編寫返回Result<T,E >的測試使您能夠在測試體中使用問號操作符,這是一種編寫測試的便捷方式,如果測試中的任何操作返回Err變量,測試就會失敗。
不能在使用Result<T,E >的測試上使用#[should_panic]批注。要斷言一個(gè)操作返回一個(gè)EErrrr變量,不要在Result<T,E >值上使用問號運(yùn)算符。而是使用assert!(value.is_err())。?文章來源:http://www.zghlxwxcb.cn/news/detail-678955.html
現(xiàn)在您已經(jīng)知道了編寫測試的幾種方法,讓我們看看運(yùn)行測試時(shí)會發(fā)生什么,并探索我們可以使用cargo test的不同選項(xiàng)。?文章來源地址http://www.zghlxwxcb.cn/news/detail-678955.html
本章重點(diǎn)
- 自動(dòng)化測試的概念
- 如何編寫測試用例
- assert!在測試用例中如何使用
- assert_eq!和assert_ne!在測試用例中如何使用
- 添加自定義失敗信息
- 使用should_panic檢查panic
- 使用Result<T, E>
到了這里,關(guān)于Rust之自動(dòng)化測試(一):如何編寫測試的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!