編寫單元測試步驟:
? ? ? ? 1. 準備所需的數據????????
? ? ? ? 2. 調用需要測試的代碼
? ? ? ? 3. 斷言運行結果與我們所期望的一致
Rust的test元數據:
????????#[cfg(test)]:
是一個屬性宏(attribute macro)。用于控制特定的代碼段僅在測試環境中編譯。
#[cfg(test)]
mod tests {// 測試函數#[test]fn my_test() {// 測試代碼}
}
?????#[cfg(test)]
的作用是確保tests
模塊內的代碼僅在執行cargo test
命令時被編譯和運行。
????????cfg
還可以用于更復雜的條件編譯場景:
#[cfg(target_os = "linux")]
fn os_specific_function() {// Linux-specific code
}#[cfg(target_os = "windows")]
fn os_specific_function() {// Windows-specific code
}
?cfg指定目標操作系統,選擇性的編譯代碼
? ? ? ? #[test] 關鍵字:表示是測試函數
? ? ? ? 測試函數:將#[test] 添加到fn關鍵字的上一行就將函數轉變為測試函數
單元測試執行命令:
? ? ? ? cargo test命令來運行測試
? ? ? ? 這個命令會構建并執行一個用于測試的可執行文件,該文件在執行過程中會逐一調用所有標注了test屬性的函數,并生成統計測試運行成功或失敗的報告。
????????Rust能夠編譯在API文檔中出現的任何代碼示例
assert_eq! 宏
assert_eq!(2 + 2, 4);
? ? ? ? 用于斷言兩個值相等。如果不相當則panic,單元測試失敗?
assert_ne!宏:
? ? ? ? 斷言兩個值不相等,單元測試則通過
????????確定它絕不可能?是某些值的時候的使用
????????總結:
????????????????assert_eq! 和assert_ne! 宏分別使用了==和!=運算符來進行判斷,并在斷言失敗時使用調試輸出格式({:?})將參數值打印出來,它們的參數必須同時實現PartialEq和Debug這兩個trait
????????????????這兩個trait都是可派生trait,可以通過在自定義的結構體或枚舉的定義的上方添加#[derive(PartialEq, Debug)]標注來自動實現這兩個trait。
assert! 宏檢查結果
????????assert! 宏由標準庫提供,它可以確保測試中某些條件的值為true
????????assert! 宏可以檢查代碼是否按照我們預期的方式運行。
????????assert! 宏接收一個能夠被計算為布爾類型的值作為參數:當這個值為true時,assert! 宏正常通過測試。當值為false時,assert! 宏就會調用panic! 宏,進而導致測試失敗。
? ? ? ? assert!宏傳入兩個值==,則等價于assert_eq!
添加自定義的錯誤提示信息
????????任何在assert!、assert_eq! 或assert_ne! 的必要參數之后出現的參數都會一起被傳遞給format! 宏:
assert!(result.contains("Carol"),"Greeting did not contain name, value was `{}`", result
)
其中,第二個參數會被傳給format!宏
should_panic檢查panic
? ? ? ? should_panic:編寫一個測試來檢查使用了非法值是否會如期發生panic
????????新屬性:should_panic。標記了這個屬性的測試函數會在代碼發生panic時順利通過,而在代碼不發生panic時執行失敗。
????????將#[should_panic]屬性放在了#[test]屬性之后、對應的測試函數之前
pub struct Guess{value:u32,
}impl Guess{pub fn new(value:u32) -> 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);}
}
????????should_panic屬性中添加可選參數expected,讓should_panic測試更加精確一些
#[should_panic(expected = "Guess value must be less than or equal to 100")]fn greater_than_100() {
????????測試某個條件會觸發帶有特定錯誤提示信息的panic!
使用Result<T, E>編寫測試:
#[cfg(test)]
mod tests{use super::*;#[test]fn it_works() -> Result<(),String>{if 2+2 == 4{Ok(())}else {Err(String::from("two plus two does not equal for"))}}
}
不要在使用Result<T, E>編寫的測試上標注#[should_panic]
編寫返回Result<T, E>的測試,就可以在測試函數體中使用問號運算符了。
在測試運行失敗時,我們應當直接返回一個Err值
控制測試的運行方式:
cargo test同樣會在測試模式下編譯代碼,并運行生成的測試二進制文件
cargo test生成的二進制文件默認會并行執行所有的測試
可以為cargo test指定命令行參數,也可以為生成的測試二進制文件指定參數:
????????分隔符--
????????cargo test --help會顯示出cargo test的可用參數
????????運行cargo test -- --help則會顯示出所有可以用在--之后的參數
并行或串行地進行測試
????????Rust會默認使用多線程來并行執行
????????開發者必須保證測試之間不會互相依賴,或者依賴到同一個共享的狀態或環境上
????????
cargo test -- --test-threads=1
? ? ? ?指定測試執行的線程數
顯示函數輸出
? ? ? ? 默認只有在測試失敗時,我們才能在錯誤提示信息的上方觀察到打印至標準輸出(println!)中的內容。
cargo test -- --nocapture
????????在測試通過時也將值打印出來
只運行部分特定名稱的測試
????????
cargo test one_hundred
給cargo test傳遞一個測試函數的名稱來單獨運行該測試
cargo test add
指定測試名稱的一部分來作為參數,任何匹配這一名稱的測試都會得到執行
通過顯式指定來忽略某些測試
#[ignore]
添加#[ignore]屬性宏
cargo test -- --ignored
通過--ignored參數單獨運行添加了#[ignore]屬性宏的測試函數
測試的組織結構:
? ? ? ? 測試分類:單元測試(unit test)和集成測試(integration test)
????????單元測試小而專注,每次只單獨測試一個模塊或私有接口
????????集成測試完全位于代碼庫之外,訪問公共接口,并且在一次測試中可能會聯用多個模塊。
標注#[cfg(test)]可以讓Rust只在執行cargo test命令時編譯和運行該部分測試代碼,而在執行cargo build時剔除它們。
不需要對集成測試標注#[cfg(test)],因為集成測試本身就放置在獨立的目錄中
單元測試:
一般將單元測試與需要測試的代碼存放在src 目錄下的同一文件中。同時也約定俗成地在每個源代碼文件中都新建一個tests模塊來存放測試函數,并使用cfg(test)對該模塊進行標注。
集成測試:
集成測試是完全位于代碼庫之外。意味著你只能調用對外公開提供的那部分接口。集成測試的目的在于驗證庫的不同部分能否協同起來正常工作
集成測試首先需要建立一個tests 目錄:項目根目錄下創建tests 文件夾,它和src 文件夾并列。Cargo會自動在這個目錄下尋找集成測試文件。我們可以在這個目錄下創建任意多個測試文件,Cargo在編譯時會將每個文件都處理為一個獨立的包。
成測試需要在代碼頂部添加語句use 包名:因為tests 目錄下的每一個文件都是一個獨立的包,所以我們需要將目標庫導入每一個測試包中
cargo test 輸出中出現了單元測試、集成測試和文檔測試這3部分
cargo test --test integration_test
cargo test時使用--test并指定文件名,可以單獨運行某個特定集成測試文件下的所有測試函數
?創建tests/common/mod.rs 將功能函數放在該文件中,這是可以被Rust理解的命名規范,rust不會將common模塊看成集成測試文件了。原因:tests 子目錄中的文件不會被視作單獨的包進行
編譯,更不會在測試輸出中擁有自己的區域。
mod common;?聲明了需要引用的模塊
二進制包的集成測試
只有代碼包(librarycrate)才可以將函數暴露給其他包來調用,而二進制包只被用于獨立執行