測試

2022-04-21 10:06 更新

測試

程序測試是一個非常有效的方法,它可以有效的暴漏程序中的缺陷,但對于暴漏缺陷來說,這還是遠遠不夠的。
—— Edsger W. Dijkstra,"卑微的程序員" (1972)

讓我們來談?wù)勅绾螠y試 Rust 代碼。我們將談?wù)摬皇鞘裁礈y試 Rust 代碼正確的方法。關(guān)于正確和錯誤地編寫測試的方式有很多的流派。所有這些方法都使用相同的基本工具,因此,我們將向您展示使用它們的語法。

測試屬性

Rust 中一個最簡單的測試是一個函數(shù),它使用 test 屬性注釋。讓我們使用 Cargo 做一個叫加法器的新項目:

    $ cargo new adder
    $ cd adder

當你做一個新項目時,Cargo 將自動生成一個簡單的測試。下面即是 src/lib.rs 的內(nèi)容:

    #[test]
    fn it_works() {
    }

注意 #[test]。該屬性表明,這是一個測試函數(shù),目前還沒有函數(shù)體。我們可以使用 Cargo test 運行這個測試:

    $ cargo test
       Compiling adder v0.0.1 (file:///home/you/projects/adder)
     Running target/adder-91b3e234d4ed382a

    running 1 test
    test it_works ... ok

    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

       Doc-tests adder

    running 0 tests

    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

Cargo 編譯和運行我們的測試。這里有兩組輸出:一個用于我們寫的測試,另一個用于文檔測試。稍后我們將討論這一問題。現(xiàn)在,讓我們來看看這一行:

    test it_works ... ok

注意 it_works。這是來自我們的函數(shù)的名稱:

    fn it_works() {

我們還得到一個總結(jié):

    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

那么為什么我們的測試能夠通過呢?任何非 panic 的測試都可以通過,任何 panic 的測試都會失敗。讓我們來看一個失敗的測試:

    #[test]
    fn it_works() {
        assert!(false);
    }

assert! 一種 Rust 提供的宏,它需要一個參數(shù):如果參數(shù)是true,什么也不會發(fā)生。如果參數(shù)是 false,它就成為 panic! 的。讓我們再次運行我們的測試:

    $ cargo test
       Compiling adder v0.0.1 (file:///home/you/projects/adder)
     Running target/adder-91b3e234d4ed382a

    running 1 test
    test it_works ... FAILED

    failures:

    ---- it_works stdout ----
    thread 'it_works' panicked at 'assertion failed: false', /home/steve/tmp/adder/src/lib.rs:3

    failures:
    it_works

    test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured

    thread '<main>' panicked at 'Some tests failed', /home/steve/src/rust/src/libtest/lib.rs:247

Rust 表明我們的測試失敗:

    test it_works ... FAILED

反映在結(jié)論中就是:

    test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured

還可以得到一個非零的狀態(tài)代碼:

    $ echo $?
    101

如果你想將 cargo test 集成到其他工具,這是非常有用的。

我們可以用另一個屬性:should_panic 轉(zhuǎn)化我們的測試的失?。?/p>

#[test]
#[should_panic]
fn it_works() {
    assert!(false);
}

如果我們 panic!,這個測試會成功,如果我們完成,則測試會失敗。讓我們來試一試:

    $ cargo test
       Compiling adder v0.0.1 (file:///home/you/projects/adder)
     Running target/adder-91b3e234d4ed382a

    running 1 test
    test it_works ... ok

    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

       Doc-tests adder

    running 0 tests

    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

Rust 提供另一個宏 assert_eq!,用來比較兩個參數(shù)是否相等:

    #[test]
    #[should_panic]
    fn it_works() {
        assert_eq!("Hello", "world");
    }

這個測試是否可以通過?因為存在 should_panic 屬性,它可以通過:

    $ cargo test
       Compiling adder v0.0.1 (file:///home/you/projects/adder)
     Running target/adder-91b3e234d4ed382a

    running 1 test
    test it_works ... ok

    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

       Doc-tests adder

    running 0 tests

    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

should_panic 測試很脆弱。很難保證測試不會因為一個意想不到的原因而失敗。為了解決這個問題,可以在 should_panic 屬性中添加一個可選的參數(shù):expected。測試工具將確保錯誤消息包含提供的文本。上面示例的安全版本是:

    #[test]
    #[should_panic(expected = "assertion failed")]
    fn it_works() {
        assert_eq!("Hello", "world");
    }

這就是所有的基礎(chǔ)讓我們來編寫一個“真正”的測試:

    pub fn add_two(a: i32) -> i32 {
        a + 2
    }

    #[test]
    fn it_works() {
        assert_eq!(4, add_two(2));
    }

這是 assert_eq! 的一個非常常見的用法:使用一些已知的參數(shù)調(diào)用某些函數(shù)并與預(yù)期的輸出比較。

測試模塊

有一種方式,以這種方式我們現(xiàn)有的例子都是不符合慣例的:它缺少測試模塊。我們的示例的慣用寫作方式,如下所示:

    pub fn add_two(a: i32) -> i32 {
    a + 2
    }

    #[cfg(test)]
    mod tests {
    use super::add_two;

    #[test]
    fn it_works() {
    assert_eq!(4, add_two(2));
    }
    }

這里有一些變化。第一個是引入帶有 cfg 屬性的 mod tests。模塊允許我們對所有的測試進行分組,如果需要也可以定義 helper 函數(shù),這個函數(shù)不會成為我們 crate 的一部分。如果目前我們試圖運行這些代碼,cfg 屬性只會編譯我們的測試代碼。這可以節(jié)省編譯時間,也保證了我們構(gòu)建的測試是完全正常的。

第二個變化是 use 聲明。因為我們在一個內(nèi)部模塊中,我們需要將我們的測試函數(shù)設(shè)置范圍。如果你有一個大的模塊,這可能就會很惱人,所以這是 glob 屬性的一種常見的使用方式。讓我們改變我們的 src/lib.rs 以便能夠使用它:

    pub fn add_two(a: i32) -> i32 {
    a + 2
    }

    #[cfg(test)]
    mod tests {
    use super::*;

    #[test]
    fn it_works() {
    assert_eq!(4, add_two(2));
    }
    }

注意 use 行的不同使用方式?,F(xiàn)在,我們來運行我們的測試:

    $ cargo test
    Updating registry `https://github.com/rust-lang/crates.io-index`
       Compiling adder v0.0.1 (file:///home/you/projects/adder)
     Running target/adder-91b3e234d4ed382a

    running 1 test
    test tests::it_works ... ok

    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

       Doc-tests adder

    running 0 tests

    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

看,運轉(zhuǎn)起來了!

當前的慣例是使用測試模塊 “unit-style” 測試。任何只測試一個小功能都是有意義的。如果用 “integration-style” 測試替代會怎么樣呢?為此,我們引出了測試目錄。

測試目錄

為了編寫集成測試,讓我們做一個測試目錄,并把一個 tests/lib.rs 文件放在里面,這是它的內(nèi)容:

    extern crate adder;

    #[test]
    fn it_works() {
    assert_eq!(4, adder::add_two(2));
    }

這類似于我們之前的測試,但略有不同。在代碼頂部有一個 extern crate adder。這是因為在測試目錄里測試是一個完全獨立的箱,所以我們需要導(dǎo)入我們的函數(shù)庫。這也是為什么 tests 是一個編寫集成風(fēng)格測試的合適的地方:他們使用函數(shù)庫和其他消費者。

讓我們運行它們:

    $ cargo test
       Compiling adder v0.0.1 (file:///home/you/projects/adder)
     Running target/adder-91b3e234d4ed382a

    running 1 test
    test tests::it_works ... ok

    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

     Running target/lib-c18e7d3494509e74

    running 1 test
    test it_works ... ok

    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

       Doc-tests adder

    running 0 tests

    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

現(xiàn)在我們有三個部分:我們之前的測試在運行,現(xiàn)在這個新的也在運行。

這就是所有的 tests 目錄。這里不需要測試模塊,因為整件事都是專注于測試的。

讓我們最后檢查一下第三部分:文檔測試。

文檔測試

沒有什么是比帶有示例的文檔更好的了。沒有什么是比不能真正工作的例子更糟的了,一直以來文檔編寫已經(jīng)改變了代碼習(xí)慣。為此,Rust 支持自動運行你的文檔中的示例。這里有一個完整的 src/lib.rs 的例子:


//! The `adder` crate provides functions that add numbers to other numbers.
//!
//! # Examples
//!
//! ```
//! assert_eq!(4, adder::add_two(2));
//! ```

/// This function adds two to its argument.
///
/// # Examples
///
/// ```
/// use adder::add_two;
///
/// assert_eq!(4, add_two(2));
/// ```
pub fn add_two(a: i32) -> i32 {
    a + 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        assert_eq!(4, add_two(2));
    }
}

注意:模塊級文檔使用 //!,函數(shù)文檔使用 ///。Rust 的文檔支持 Markdown 中評論,所以三重斜線標志代碼塊。包含 # Examples 部分是一種慣例,以下所示。

讓我們再次運行測試:

    $ cargo test
       Compiling adder v0.0.1 (file:///home/steve/tmp/adder)
     Running target/adder-91b3e234d4ed382a

    running 1 test
    test tests::it_works ... ok

    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

     Running target/lib-c18e7d3494509e74

    running 1 test
    test it_works ... ok

    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

       Doc-tests adder

    running 2 tests
    test add_two_0 ... ok
    test _0 ... ok

    test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured

現(xiàn)在我們運行了所有三種測試!注意這些測試文檔的名稱:the_0 生成模塊測試,add_two_0 生成功能測試。當你添加更多的例子,這些名字會自動增量(例如 add_two_1)。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號