俗話說:「測試寫得好,獎金少不了。」 bash
有經驗的開發人員一般會經過單元測試來保證代碼基本邏輯的正確性。若是你是一名新手開發者,而且還沒體會到單元測試的好處,那麼建議你先讀一下我以前的一篇文章代碼潔癖系列(七):單元測試的地位。函數
寫單元測試通常須要三個步驟:單元測試
瞭解了這些之後,咱們就來看看在Rust中應該怎麼寫單元測試。測試
首先咱們創建一個library項目命令行
$ cargo new adder --lib Created library `adder` project
而後在src/lib.rs文件中開始寫測試代碼線程
#[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); } }
此時在命令行運行cargo test
就會獲得測試結果3d
能夠看到,結果顯示,Rust運行了一項測試而且測試經過。後面的Doc-tests咱們先放下,之後再聊。code
固然,這並非咱們常見的測試,在平常開發中,咱們一般是先寫咱們的業務代碼而後再對各個函數進行單元測試,最後還會對某個模塊進行集成測試。那麼咱們就來模擬一下平常開發過程當中應該如何來寫測試。blog
咱們仍然是用上面的項目,先來在src/lib.rs中寫一段「業務代碼」開發
pub fn add_two(a: i32) -> i32 { internal_adder(a, 2) } fn internal_adder(a: i32, b: i32) -> i32 { a + b }
這是一段很是簡單的代碼,對外暴露的函數只是一個加2的功能,內部調用了一個兩數相加的函數。如今咱們就對這個內部函數作一個單元測試。
#[cfg(test)] mod tests { use super::*; #[test] fn internal() { assert_eq!(4, internal_adder(2, 2)); } }
在測試模塊中,若是想要使用咱們業務代碼中的函數,就須要經過use super::*;
將其引入可用範圍。接着,仍是執行cargo test
,測試結果與剛纔相似。
測了半天全是經過的沒什麼意思,單元測試真正的做用是要發現代碼中的問題,因此咱們來嘗試一個錯誤的試一下。假設咱們但願2+2等於5。
這裏咱們的assert_eq!左右不相等,引發了線程恐慌,所以致使測試失敗。結果中給出了失敗的緣由,引發失敗的位置,而且有一句提示:note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
咱們按照這個提示,設置變量RUST_BACKTRACE=1,此時再執行cargo test
。
Rust就會將錯誤棧打印出來,根據結果提示,這並非完整的錯誤棧,咱們還能夠將RUST_BACKTRACE設置爲full來查看更加詳細的信息。這裏我就不作演示了。
接下來咱們再演示一下集成測試。咱們一般將集成測試單獨放到一個目錄中,在lib.rs文件中,rust識別測試mod的名稱是tests,一樣的,咱們在src下建立tests目錄。tests目錄下就是咱們的全部集成測試代碼。
如圖,integration_test是咱們測試代碼的文件,common目錄下的mod.rs文件中是一些集成測試必要的配置。這裏咱們只是放了一個空的setup函數。
在集成測試中,咱們就要像正常他人使用咱們的代碼時那樣來進行測試,首先須要將咱們的mod引入到可用範圍,固然還須要加上common的mod。
use adder; mod common; #[tests] fn it_adds_two() { common::setup(); assert_eq!(4, adder::add_two(2)); }
接着就能夠測試咱們對外暴露的函數了。
ok,集成測試的方法咱們也掌握了。如今來看看一直被咱們忽略的Doc-tests吧。
咱們已經知道,Rust中的註釋是雙斜線//
,像咱們剛剛寫的library代碼,若是想要把它發佈到crate.io上讓別人使用,那麼咱們就須要增長相應的文檔,這裏文檔的每行都應該是三斜線///
開頭,而文檔中也應該放一些例子供他人蔘考。
/// Adds two to the number given. /// /// # Examples /// /// ``` /// let arg = 5; /// let answer = adder::add_two(arg); /// /// assert_eq!(7, answer); /// ``` pub fn add_two(a: i32) -> i32 { internal_adder(a, 2) }
如今我給add_two函數加上了文檔,咱們再次執行cargo test
命令。
如今咱們就明白了,Doc-tests測試就是運行咱們文檔中的例子。
到目前爲止,咱們已經知道了在Rust中如何寫測試代碼了。接下來咱們再來了解幾個比較經常使用的特性。
咱們在開發過程當中確定不會每次都去跑全量的單元測試,那樣太浪費時間了。一般是咱們開發完一個功能以後,編寫對應的單元測試,而後單獨跑這個測試。那麼Rust中能不能單獨跑一個單元測試呢?答案是確定的。
相信細心的同窗已經發現了,Rust測試結果中,是針對每一個測試單獨統計結果,而且每一個測試都有本身的名字,像咱們前面寫的it_works
和internal
。假設咱們的代碼中同時存在這兩個函數,若是你想要單獨跑internal這一個測試,就可使用cargo test internal
命令。
你也可使用這種方法來執行多個名稱相似的測試,假如咱們有名稱爲internal_a
的測試,那麼執行cargo test internal
命令時它也會被執行。
當咱們有一個測試執行時間很是長的時候,咱們通常不會輕易去執行,這時若是你想要執行多個測試,除了用咱們上面提到的方法,去指定不一樣的名稱列表之外。還能夠把這個測試忽略掉。
如今我不想執行internal
測試了,只須要對代碼進行以下改動:
#[test] #[ignore] fn internal() { assert_eq!(4, internal_adder(2, 2)); }
這時再來運行測試,結果如圖所示。
咱們發現此時internal
測試已經被忽略了。
除了測試代碼邏輯正常的狀況,咱們有時還須要測試一些異常狀況,好比接收到非法參數時程序可否返回咱們但願看到的異常。
咱們首先來看一下如何測試程序返回異常信息。
Rust爲咱們提供了一個叫作should_panic的註解。咱們可使用它來測試程序是否返回異常:
pub fn add_two(a: i32) -> i32 { internal_adder(a, 2) } fn internal_adder(a: i32, b: i32) -> i32 { if a < 0 { panic!("a should bigger than 0"); } a + b } #[cfg(test)] mod tests { use super::*; #[test] #[should_panic] fn internal() { assert_eq!(4, internal_adder(-2, 2)); } }
此時咱們運行測試時就會發現internal測試經過,由於它發生了線程恐慌,這是咱們但願看到的結果。
另外,咱們還能夠再指定咱們具體指望的異常,那麼就能夠在should_panic後面加上expected參數。
#[test] #[should_panic(expected = "a should be positive")] fn internal() { assert_eq!(4, internal_adder(-2, 2)); }
你們能夠自行運行一下這段測試代碼看看效果。
文中我向你們介紹了在Rust中如何進行單元測試、集成測試,還有比較特殊的文檔測試。最後還介紹了3種常見的測試特性。
最後想友情提醒你們一下,在開發過程當中,不要寫完一堆功能後再開始寫單元測試,這時你頗有可能會由於測試代碼過於繁瑣而放棄。建議你們每寫一個功能,隨即開始進行單元測試,這樣也能當即看到本身的代碼的執行效果,提升成就感。這就是所謂的「步步爲營」。