測試從來不是一件簡單的事情,我本人深有體會!
書本作者引用了很重要的話:軟體測試是證明 bug 存在的有效方法,而證明其不存在時則顯得令人絕望的不足 (Edsger W. Dijkstra 在其 1972 年的文章【謙卑的程式設計師】(“The Humble Programmer”))
注:Edsger W. Dijkstra在1972獲得圖靈獎
本人學過一些語言,看過不少類似書籍,但是在書籍中間部分就讓入門者注意測試,的確是挺特別的。
這充分地體現了作者對測試的重視。
既然專家都這麼注意測試,我輩自當也需要格外注意。Rust由於其語言特性,應該是需要更加重視測試!
把測試當做無足輕重,或者認為不需要什麼技術水平的,都是不專業的行為!it新人或者一些水平有問題的常常會有這個方面的看法!
本章節的內容比較多,但只要按照作者的例子作一遍,那麼基本能夠掌握!
rust測試需要用到cargo test命令,這個命令有許多選項,部分會在本章節中提到,還有許多是沒有提到的。
把test命令的各個選項都熟悉下,也需要耗費不少時間!
本文所涉及到內容都是入門級別的,屬於基本的粗淺的。但是掌握這些基本上已經可以做需要的測試了。
至於測試技巧,是另外一門學問了,和rust沒有關係。
一、編寫單元測試程式碼
本例模仿書上的,在庫中編寫單元測試程式碼。
按照慣例:建立一個tests模組,並在tests下編寫測試程式碼
關鍵詞:
#[cfg(test)] -- 告訴編譯器執行的時候不要編輯
#[test] -- 告訴編譯器這是一個測試單元
函式: assert_eq!,assert_ne!,assert!
二、控制測試
關鍵問題:
- 如何控制併發
- 如何控制需要測試的
- 如何忽略,或者如何只測試被標記為忽略的
控制併發
預設是多執行緒,但也可以透過一下方式指定需要的執行緒
控制測試目標
忽略
cargo test -- --ignored 只測試被忽略的
三、測試的組織結構
如何做單元測試
如何做整合測試
建立和src同i的目錄tests,在tests下放任意個rs,每個rs都是一個單元包
每個單元包都可以應用tests的公共模組。
整合測試中的公共模組
使用老的模組定義規範:目錄+mod.rs
如上圖,haha模組在tests/haha下,其下有mod.rs
四、完整測試程式碼和示例
這個例子是基於一個庫專案,有三個檔案:
src/lib.rs 庫 ,單元測試檔案
tests/itest1.rs 整合測試檔案之一
tests/haha/mod.rs 整合測試的模組haha
lib.rs
pub fn add(left: u64, right: u64) -> u64 { left + right } #[cfg(test)] mod tests { use super::add; //測試宏 assert_eq! #[test] fn it_works1() { let result = add(2, 2); assert_eq!(result, 4); } //測試宏 assert!.並測試自定義的錯誤資訊 #[test] fn it_works2() { let result = add(1, 3); assert!(result==5,"1+3={},而不是5",result); } //測試宏 assert_ne! #[test] fn it_works3() { let result = add(1, 4); println!("這一句將在--show-output 的時候列印:1+4={}",result); assert_ne!(result,6); } //測試fail! #[test] #[should_panic] fn it_fail() { //fail!("this test will fail"); let result = add(1, 4); assert_eq!(result,6); } #[test] #[should_panic] fn access_outof_range() { let mut v = vec![1,2]; v[2] = 3; } //如果返回Err,則測試失敗 #[test] fn test_result()->Result<(),String> { if 1==2 { Ok(()) } else { Err("error".to_string()) } } #[test] #[ignore] fn test_ignore() { assert_eq!(1,2, "1!={}",2); } }
itest1.rs
use lzfmath::*; mod haha; #[test] fn itest_add() { haha::haha(); assert_eq!(add(1, 2), 3); }
mod.rs
pub fn haha(){ println!("哈哈!"); }
4.1標準單元測試結果
4.2設定併發和檢視標準輸出
4.3只測試it開頭的單元測試
4.4只測試被忽略的
4.5整合測試+整合測試模組
五、小結
程式碼測試主要有三個部分內容:
1.如何編寫測試
主要涉及到幾個編譯標記/指令和幾個函式/宏,Result
編譯指令
a.#[cfg(test)] 一個條件編譯指令,用於指定緊隨其後的程式碼塊或項(如函式、模組等)僅在測試環境下編譯
b.#[test] 函式測試標記,cargo的測試的時候,會找出有這些標記的函式,並執行測試
c.#[should_panic] 函式測試標記,標記的函式會期待panic,如果函式沒有panic就會測試失敗。
函式
a.assert! 斷定表示式為真。如果為真就是成功
b.assert_eq! 斷定兩個值是否相等。如果相等就是成功
c.assert_ne! 斷定兩個值是否不相等。如果不相等就是成功
d.assert_matches! 斷定表示式匹配模式。如果匹配就是成功.特別適用於列舉和結構等型別
f.fail! 斷定測試失敗,並列印出字串
assert類函式,可以在標準引數之外再傳遞其它引數,比如失敗時列印的字串。rust會呼叫格式化字串,並列印出引數。
Result型別
a.Result<T,E> 列舉型別,表示成功或失敗。T為成功時的型別,E為錯誤型別
2.如何控制測試
預設的測試行為
a.cargo test 在測試模式下編譯程式碼並執行生成的測試二進位制檔案
b.cargo test 產生的二進位制檔案的預設行為是併發執行所有的測試,並截獲測試執行過程中產生的輸出,阻止它們被顯示出來,使得閱讀測試結果相關的內容變得更容易
如何控制(控制cargo test的行為)
a.控制併發 -- --test-threads=1 只允許一個執行緒執行測試
b.控制輸出 -- --show-output 顯示測試執行過程中產生的輸出。這樣可以輸出透過部分的列印輸出
c.控制測試 <name> 只執行指定名稱的測試 。注意這是模糊匹配
add 測試包含add的測試。即cargo test add
--test xxx 指定具體檔名 cargo test --test itest1 測試檔名帶itest1的測試
忽略專案
a.#[ignore] 在程式碼中標記需要忽略的內容
b.測試被忽略專案 -- --ignored ,這樣只會測試被標記為#[ignore]的內容
示例
cargo test result -- --test-threads=2 --show-output 在開啟兩個執行緒的情況下,測試包含result的測試,同時那些產生的輸出也會顯示出來
cargo test -- --ignored 測試被標記為#[ignore]的內容
3.測試的組織結構
如何作單元測試
a.固定的規範-- 使用#[cfg(test)] 標記測試程式碼,並使用#[test]標記測試函式。其次模組通常也命名位tests
如何作整合測試
a.在專案下建立tests目錄,然後在該目錄下建立測試檔案 。注意tests目錄和src是同級的. 這個目錄應該是可以是別的,也許可以透過test的選項來指定
b.在測試目錄tests可以建立任意多個檔案,cargo會把每一個檔案當做一個單元包進行測試
c.執行 cargo test即可,但是這樣會把單元測試一起執行。
d.透過指定整合測試檔案的方式,可以只執行指定的測試檔案 cargo test --test itest1.也可以透過指定測試名稱的方式
carggo test xxx
綜合起來,cargo test --test xxx 也許是更好的選擇。
整合測試的公共模組
a.必須先建立一個目錄,假設是haha
b.在haha下建立mod.rs檔案
c.tests下的其它rs可以呼叫haha,並且haha不會被測試。haha的內容只會被整合測試程式碼呼叫。
也就是說整合測試目錄下的模組組織方式,反而使用的是舊的模組定義規範:基於目錄+mod.rs
為什麼很多專案既有src/main.rs,src/lib.rs
這都是為了方便測試。
引入在整合測試中匯入src/main.rs的模組並不容易。 main.rs是一個二進位制檔案,而lib.rs是庫。