rust學習十二、測試

正在战斗中發表於2024-11-27

測試從來不是一件簡單的事情,我本人深有體會!

書本作者引用了很重要的話:軟體測試是證明 bug 存在的有效方法,而證明其不存在時則顯得令人絕望的不足 (Edsger W. Dijkstra 在其 1972 年的文章【謙卑的程式設計師】(“The Humble Programmer”))

注:Edsger W. Dijkstra在1972獲得圖靈獎

本人學過一些語言,看過不少類似書籍,但是在書籍中間部分就讓入門者注意測試,的確是挺特別的。

這充分地體現了作者對測試的重視。

既然專家都這麼注意測試,我輩自當也需要格外注意。Rust由於其語言特性,應該是需要更加重視測試!

把測試當做無足輕重,或者認為不需要什麼技術水平的,都是不專業的行為!it新人或者一些水平有問題的常常會有這個方面的看法!

本章節的內容比較多,但只要按照作者的例子作一遍,那麼基本能夠掌握!

rust測試需要用到cargo test命令,這個命令有許多選項,部分會在本章節中提到,還有許多是沒有提到的。

把test命令的各個選項都熟悉下,也需要耗費不少時間!

本文所涉及到內容都是入門級別的,屬於基本的粗淺的。但是掌握這些基本上已經可以做需要的測試了。

至於測試技巧,是另外一門學問了,和rust沒有關係。

一、編寫單元測試程式碼

本例模仿書上的,在庫中編寫單元測試程式碼。

按照慣例:建立一個tests模組,並在tests下編寫測試程式碼

關鍵詞:

#[cfg(test)] -- 告訴編譯器執行的時候不要編輯

#[test] -- 告訴編譯器這是一個測試單元

#[should_panic] - 告訴測試程式,如果不出意外就是失敗

函式: assert_eq!,assert_ne!,assert!

二、控制測試

關鍵問題:

  1. 如何控制併發
  2. 如何控制需要測試的
  3. 如何忽略,或者如何只測試被標記為忽略的

控制併發

預設是多執行緒,但也可以透過一下方式指定需要的執行緒

-- --test-threads=n

控制測試目標

cargo test xxx ,xxx是測試的名稱,即單元測試方法的名稱.屬於模糊匹配
cargo test --test xxx 指定整合測試的檔名,模糊匹配

忽略

#[ingore] -- 用於標記測試方法為被忽略

cargo test -- --ignored 只測試被忽略的

三、測試的組織結構

如何做單元測試

固定的規範-- 使用#[cfg(test)] 標記測試程式碼,並使用#[test]標記測試函式。其次模組通常也命名位tests

如何做整合測試

建立和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是庫。

相關文章