【Rust】Rust學習 第十一章編寫自動化測試

Rust 是一個相當注重正確性的編程語言,不過正確性是一個難以證明的復雜主題。Rust 的類型系統在此問題上下了很大的功夫,不過它不可能捕獲所有種類的錯誤。為此,Rust 也在語言本身包含了編寫軟件測試的支持。

編寫一個叫做?add_two?的將傳遞給它的值加二的函數。它的簽名有一個整型參數并返回一個整型值。當實現和編譯這個函數時,Rust 會進行所有目前我們已經見過的類型檢查和借用檢查,例如,這些檢查會確保我們不會傳遞?String?或無效的引用給這個函數。Rust 所?不能?檢查的是這個函數是否會準確的完成我們期望的工作:返回參數加二后的值,而不是比如說參數加 10 或減 50 的值!這也就是測試出場的地方。

可以編寫測試斷言,比如說,當傳遞?3?給?add_two?函數時,返回值是?5。無論何時對代碼進行修改,都可以運行測試來確保任何現存的正確行為沒有被改變。

11.1?編寫測試

如何編寫測試

Rust 中的測試函數是用來驗證非測試代碼是否按照期望的方式運行的。測試函數體通常執行如下三種操作:

  1. 設置任何所需的數據或狀態
  2. 運行需要測試的代碼
  3. 斷言其結果是我們所期望的

測試函數剖析

作為最簡單例子,Rust 中的測試就是一個帶有?test?屬性注解的函數。屬性(attribute)是關于 Rust 代碼片段的元數據;第五章中結構體中用到的?derive?屬性就是一個例子。為了將一個函數變成測試函數,需要在?fn?行之前加上?#[test]當使用?cargo test?命令運行測試時,Rust 會構建一個測試執行程序用來調用標記了?test?屬性的函數,并報告每一個測試是通過還是失敗。

創建一個新的庫項目?adder

$ cargo new adder --libCreated library `adder` project
$ cd adder

新建后的默認代碼是,判斷加法

pub fn add(left: usize, right: usize) -> usize {left + right
}#[cfg(test)]
mod tests {use super::*;#[test]fn it_works() {let result = add(2, 2);assert_eq!(result, 4);}
}

使用

cargo test

結果

Cargo 編譯并運行了測試。在?CompilingFinished?和?Running?這幾行之后,可以看到?running 1 test?這一行。下一行顯示了生成的測試函數的名稱,它是?it_works,以及測試的運行結果,ok。接著可以看到全體測試運行結果的摘要:test result: ok.?意味著所有測試都通過了。1 passed; 0 failed?表示通過或失敗的測試數量。

因為之前我們并沒有將任何測試標記為忽略,所以摘要中會顯示?0 ignored。我們也沒有過濾需要運行的測試,所以摘要中會顯示0 filtered out

0 measured?統計是針對性能測試的。性能測試(benchmark tests)在編寫本書時,仍只能用于 Rust 開發版(nightly Rust)。

測試輸出中的以?Doc-tests adder?開頭的這一部分是所有文檔測試的結果。我們現在并沒有任何文檔測試,不過 Rust 會編譯任何在 API 文檔中的代碼示例。這個功能幫助我們使文檔和代碼保持同步!

改變測試的名稱并看看這如何改變測試的輸出。修改測名稱

pub fn add(left: usize, right: usize) -> usize {left + right
}#[cfg(test)]
mod tests {use super::*;#[test]// 這里修改了測試名稱fn exploration() {let result = add(2, 2);assert_eq!(result, 4);}
}

結果

讓我們增加另一個測試,不過這一次是一個會失敗的測試!當測試函數中出現 panic 時測試就失敗了。每一個測試都在一個新線程中運行,當主線程發現測試線程異常了,就將對應測試標記為失敗。第九章講到了最簡單的造成 panic 的方法:調用?panic!?宏。

pub fn add(left: usize, right: usize) -> usize {left + right
}#[cfg(test)]
mod tests {use super::*;#[test]fn exploration() {let result = add(2, 2);assert_eq!(result, 4);}// 新增錯誤測試#[test]fn another() {panic!("Make this test fail");}}

結果

再次?cargo test?運行測試。它表明?exploration?測試通過了而?another?失敗了

test tests::another?這一行是?FAILED?而不是?ok?了。在單獨測試結果和摘要之間多了兩個新的部分:第一個部分顯示了測試失敗的詳細原因。在這個例子中,another?因為在src/lib.rs?的第 10 行?panicked at 'Make this test fail'?而失敗。下一部分列出了所有失敗的測試,這在有很多測試和很多失敗測試的詳細輸出時很有幫助。

最后是摘要行:總體上講,測試結果是?FAILED。有一個測試通過和一個測試失敗。

使用assert!宏來檢查結果

assert!?宏由標準庫提供,在希望確保測試中一些條件為?true?時非常有用。需要向?assert!?宏提供一個求值為布爾值的參數。如果值是?trueassert!?什么也不做,同時測試會通過。如果值為?falseassert!?調用?panic!?宏,這會導致測試失敗。assert!?宏幫助我們檢查代碼是否以期望的方式運行。


// 結構體
struct Rectangle {width: u32,height: u32,
}// 結構體實現了can_hold方法
impl Rectangle {fn can_hold(&self, other: &Rectangle) -> bool {self.width > other.width && self.height > other.height}
}// 測試
#[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));}
}

注意在?tests?模塊中新增加了一行:use super::*;

我們將測試命名為?larger_can_hold_smaller,并創建所需的兩個?Rectangle?實例。接著調用?assert!?宏并傳遞?larger.can_hold(&smaller)?調用的結果作為參數。這個表達式預期會返回?true,所以測試應該通過。

結果

再來增加另一個測試,這一回斷言一個更小的矩形不能放下一個更大的矩形:

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, height: 7 };let smaller = Rectangle { width: 5, height: 1 };assert!(!smaller.can_hold(&larger));}
}

?也通過了

如果引入一個 bug 的話測試結果會發生什么。將?can_hold?方法中比較長度時本應使用大于號的地方改成小于號:

impl Rectangle {fn can_hold(&self, other: &Rectangle) -> bool {self.width < other.width && self.height > other.height}
}

結果

我們的測試捕獲了 bug!因為?larger.length?是 8 而?smaller.length?是 5,can_hold?中的長度比較現在因為 8 不小于 5 而返回?false

使用assert_eq!和assert_ne!宏來測試相等

測試功能的一個常用方法是將需要測試代碼的值與期望值做比較,并檢查是否相等。可以通過向?assert!?宏傳遞一個使用?==?運算符的表達式來做到。不過這個操作實在是太常見了,以至于標準庫提供了一對宏來更方便的處理這些操作 ——?assert_eq!?和?assert_ne!。這兩個宏分別比較兩個值是相等還是不相等。當斷言失敗時他們也會打印出這兩個值具體是什么,以便于觀察測試?為什么?失敗,而?assert!?只會打印出它從?==?表達式中得到了?false?值,而不是導致?false?的兩個值。

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));}
}

傳遞給?assert_eq!?宏的第一個參數?4?,等于調用?add_two(2)?的結果。測試中的這一行?test tests::it_adds_two ... ok?中?ok?表明測試通過!

在代碼中引入一個 bug 來看看使用?assert_eq!?的測試失敗是什么樣的。

pub fn add_two(a: i32) -> i32 {a + 3      // 這里修改了
}#[cfg(test)]
mod tests {use super::*;#[test]fn it_adds_two() {assert_eq!(4, add_two(2));}
}

結果

測試捕獲到了 bug!it_adds_two?測試失敗,顯示信息?assertion failed: `(left == right)`?并表明?left?是?4?而?right?是?5。這個信息有助于我們開始調試:它說?assert_eq!?的?left?參數是?4,而?right?參數,也就是?add_two(2)?的結果,是?5

需要注意的是,在一些語言和測試框架中,斷言兩個值相等的函數的參數叫做?expected?和?actual,而且指定參數的順序是很關鍵的。然而在 Rust 中,他們則叫做?left?和?right,同時指定期望的值和被測試代碼產生的值的順序并不重要。這個測試中的斷言也可以寫成?assert_eq!(add_two(2), 4),這時失敗信息會變成?assertion failed: `(left == right)`?其中?left?是?5?而?right?是?4

assert_ne!?宏在傳遞給它的兩個值不相等時通過,而在相等時失敗。

自定義失敗信息

也可以向?assert!assert_eq!?和?assert_ne!?宏傳遞一個可選的失敗信息參數,可以在測試失敗時將自定義失敗信息一同打印出來。任何在?assert!?的一個必需參數和?assert_eq!?和?assert_ne!?的兩個必需參數之后指定的參數都會傳遞給?format!?宏,所以可以傳遞一個包含?{}?占位符的格式字符串和需要放入占位符的值。自定義信息有助于記錄斷言的意義;當測試失敗時就能更好的理解代碼出了什么問題。

例如,比如說有一個根據人名進行問候的函數,而我們希望測試將傳遞給函數的人名顯示在輸出中:

?

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"));}
}

結果

這個程序的需求還沒有被確定,因此問候文本開頭的?Hello?文本很可能會改變。然而我們并不想在需求改變時不得不更新測試,所以相比檢查?greeting?函數返回的確切值,我們將僅僅斷言輸出的文本中包含輸入參數。

讓我們通過將?greeting?改為不包含?name?來在代碼中引入一個 bug 來測試失敗時是怎樣的:

pub fn greeting(name: &str) -> String {String::from("Hello!")
}

結果

結果僅僅告訴了我們斷言失敗了和失敗的行號。一個更有用的失敗信息應該打印出?greeting?函數的值。讓我們為測試函數增加一個自定義失敗信息參數:帶占位符的格式字符串,以及?greeting?函數的值:

#[test]
fn greeting_contains_name() {let result = greeting("Carol");assert!(result.contains("Carol"),"Greeting did not contain name, value was `{}`", result);
}

結果

使用should_panic檢查panic

除了檢查代碼是否返回期望的正確的值之外,檢查代碼是否按照期望處理錯誤也是很重要的。

可以通過對函數增加另一個屬性?should_panic?來實現這些。這個屬性在函數中的代碼 panic 時會通過,而在其中的代碼沒有 panic 時失敗。

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);}
}

結果

看起來不錯!現在在代碼中引入 bug,移除?new?函數在值大于 100 時會 panic 的條件:

fn main() {}
pub struct Guess {value: i32,
}// --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}}
}

結果

這回并沒有得到非常有用的信息,不過一旦我們觀察測試函數,會發現它標注了?#[should_panic]。這個錯誤意味著代碼中測試函數?Guess::new(200)?并沒有產生 panic。

將Result<T,E>用于測試

也可以使用?Result<T, E>?編寫測試!這里是第一個例子采用了 Result:


#![allow(unused_variables)]
fn main() {
#[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?函數的返回值類型為?Result<(), String>。在函數體中,不同于調用?assert_eq!?宏,而是在測試通過時返回?Ok(()),在測試失敗時返回帶有?String?的?Err

這樣編寫測試來返回?Result<T, E>?就可以在函數體中使用問號運算符,如此可以方便的編寫任何運算符會返回?Err?成員的測試。

不能對這些使用?Result<T, E>?的測試使用?#[should_panic]?注解。相反應該在測試失敗時直接返回?Err?值。

11.2?運行測試

11.3?測試的組織結構

用到再學

參考:?測試 - Rust 程序設計語言 簡體中文版 (bootcss.com)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/35780.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/35780.shtml
英文地址,請注明出處:http://en.pswp.cn/news/35780.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[C++ 網絡協議編程] TCP/IP協議

目錄 1. TCP/IP協議棧 2. TCP原理 2.1 TCP套接字中的I/O緩沖 2.2 TCP工作原理 2.2.1 三次握手&#xff08;連接&#xff09; 2.2.2 與對方主機的數據交換 2.2.3 四次握手&#xff08;斷開與套接字的連接&#xff09; TCP&#xff08;Transmission Control Protocol傳輸控…

無涯教程-Perl - ref函數

描述 如果EXPR為引用,則此函數返回真值&#xff1b;如果未提供EXPR,則為$_。返回的實際值還定義了引用所引用的實體的類型。 內置類型為- REFSCALARARRAYHASHCODEGLOBLVALUEIO::Handle 如果使用bless()函數為變量設置了祝福,則將返回新的數據類型。新的數據類型通常將是一個…

比較編程語言C和Go

使用一個簡單的計數程序來比較古老的C語言和現代的Go語言。Go是一種現代的編程語言&#xff0c;它在很大程度上源自C語言。因此&#xff0c;對于任何使用C語言編寫程序的人來說&#xff0c;Go可能會感覺很熟悉。Go使得編寫新程序變得容易&#xff0c;同時又讓C程序員感到熟悉&a…

大數據-玩轉數據-Flink 自定義Sink(Mysql)

一、說明 如果Flink沒有提供給我們可以直接使用的連接器&#xff0c;那我們如果想將數據存儲到我們自己的存儲設備中&#xff0c;mysql 的安裝使用請參考 mysql-玩轉數據-centos7下mysql的安裝 創建表 CREATE TABLE sensor (id int(10) ) ENGINEInnoDB DEFAULT CHARSETutf8二…

二 根據用戶行為數據創建ALS模型并召回商品

二 根據用戶行為數據創建ALS模型并召回商品 2.0 用戶行為數據拆分 方便練習可以對數據做拆分處理 pandas的數據分批讀取 chunk 厚厚的一塊 相當大的數量或部分 import pandas as pd reader pd.read_csv(behavior_log.csv,chunksize100,iteratorTrue) count 0; for chunk in …

DNS協議及其工作原理

DNS是域名系統&#xff08;Domain Name System&#xff09;的縮寫&#xff0c;它是一種用于將域名轉換為IP地址的分布式數據庫系統。它是因特網的基石&#xff0c;能夠使人們通過域名方便地訪問互聯網&#xff0c;而無需記住復雜的IP地址。 DNS的歷史可以追溯到1983年&#xf…

4個簡化IT服務臺任務的ChatGPT功能

最近幾個月&#xff0c;ChatGPT 風靡全球&#xff0c;這是一個 AI 聊天機器人&#xff0c;使用戶能夠生成腳本、文章、鍛煉圖表等。這項技術在各行各業都有無窮無盡的應用&#xff0c;在本文中&#xff0c;我們將研究這種現代技術如何幫助服務臺團隊增強服務交付和客戶體驗。 什…

最佳實踐:如何優雅地提交一個 Amazon EMR Serverless 作業?

《大數據平臺架構與原型實現&#xff1a;數據中臺建設實戰》一書由博主歷時三年精心創作&#xff0c;現已通過知名IT圖書品牌電子工業出版社博文視點出版發行&#xff0c;點擊《重磅推薦&#xff1a;建大數據平臺太難了&#xff01;給我發個工程原型吧&#xff01;》了解圖書詳…

章節7:XSS檢測和利用

章節7&#xff1a;XSS檢測和利用 測試payload <script>alert(XSS)</script> <script>alert(document.cookie)</script> ><script>alert(document.cookie)</script> ><script>alert(document.cookie)</script> &qu…

元宇宙之經濟(02)理解NFT

1 NFT是什么&#xff1f; 想象一下&#xff0c;你小時候曾經在操場上集齊過各種不同的貼紙&#xff0c;然后和朋友們交換&#xff0c;這些貼紙有著獨特的圖案和價值。NFT的概念與此類似&#xff0c;但在數字世界中運作。NFT是一種基于區塊鏈技術的數字資產&#xff0c;每個NFT…

golang—面試題大全

目錄標題 sliceslice和array的區別slice擴容機制slice是否線程安全slice分配到棧上還是堆上擴容過程中是否重新寫入go深拷貝發生在什么情況下&#xff1f;切片的深拷貝是怎么做的copy和左值進行初始化區別slice和map的區別 mapmap介紹map的key的類型map對象如何比較map的底層原…

《Java極簡設計模式》第03章:工廠方法模式(FactoryMethod)

作者&#xff1a;冰河 星球&#xff1a;http://m6z.cn/6aeFbs 博客&#xff1a;https://binghe.gitcode.host 文章匯總&#xff1a;https://binghe.gitcode.host/md/all/all.html 源碼地址&#xff1a;https://github.com/binghe001/java-simple-design-patterns/tree/master/j…

無法正確識別車牌(Python、OpenCv、Tesseract)

我正在嘗試識別車牌&#xff0c;但出現了錯誤&#xff0c;例如錯誤/未讀取字符 以下是每個步驟的可視化&#xff1a; 從顏色閾值變形關閉獲得遮罩 以綠色突出顯示的車牌輪廓過濾器 將板輪廓粘貼到空白遮罩上 Tesseract OCR的預期結果 BP 1309 GD 但我得到的結果是 BP 1309…

騰訊云標準型CVM云服務器詳細介紹

騰訊云CVM服務器標準型實例的各項性能參數平衡&#xff0c;標準型云服務器適用于大多數常規業務&#xff0c;例如&#xff1a;web網站及中間件等&#xff0c;常見的標準型云服務器有CVM標準型S5、S6、SA3、SR1、S5se等規格&#xff0c;騰訊云服務器網來詳細說下云服務器CVM標準…

NAS搭建指南一——服務器的選擇與搭建

一、服務器的選擇 有自己的本地的公網 IP 的請跳過此篇文章按需求選擇一個云服務器&#xff0c;目的就是為了進行 frp 的搭建&#xff0c;完成內網穿透我選擇的是騰訊云服務器&#xff0c;我的配置如下&#xff0c;僅供參考&#xff1a; 4. 騰訊云服務器官網地址 二、服務器…

docker 鏡像的導出與導入 save 與 load

一、鏡像導出 docker save 導出 將系統中的鏡像保存為壓縮包&#xff0c;進行文件傳輸。使用 docker save --help 查看命令各參數&#xff0c;或者去docker官網查看.以 hello-world鏡像為例。 A&#xff1a;將鏡像保存為tar包 docker save image > package.tar docker sa…

day9 10-牛客67道劍指offer-JZ66、19、20、75、23、76、8、28、77、78

文章目錄 1. JZ66 構建乘積數組暴力解法雙向遍歷 2. JZ19 正則表達式匹配3. JZ20 表示數值的字符串有限狀態機遍歷 4. JZ75 字符流中第一個不重復的字符5. JZ23 鏈表中環的入口結點快慢指針哈希表 6. JZ76 刪除鏈表中重復的結點快慢指針三指針如果只保留一個重復結點 7. JZ8 二…

gitblit-使用

1.登入GitBlit服務器 默認用戶和密碼: admin/admin 2.創建一個新的版本庫 點擊圖中的“版本庫”&#xff0c;然后點擊圖中“創建版本庫” 填寫名稱和描述&#xff0c;注意名稱最后一定要加 .git選擇限制查看、克隆和推送勾選“加入README”和“加入.gitignore文件”在圖中的1處…

使用IIS服務器部署Flask python Web項目

參考文章 ""D:\Program Files (x86)\Python310\python310.exe"|"D:\Program Files (x86)\Python310\lib\site-packages\wfastcgi.py"" can now be used as a FastCGI script processor參考文章 請求路徑填寫*&#xff0c;模塊選擇FastCgiModule&…

一鍵部署 Umami 統計個人網站訪問數據

談到網站統計&#xff0c;大家第一時間想到的肯定是 Google Analytics。然而&#xff0c;我們都知道 Google Analytics 會收集所有用戶的信息&#xff0c;對數據沒有任何控制和隱私保護。 Google Analytics 收集的指標實在是太多了&#xff0c;有很多都是不必要的&#xff0c;…