控制程式如何執行

就像 cargo run 會編譯你的程式碼並執行產生的二進制檔案,cargo test 會在測試模式編譯你的程式碼並執行產生的測試二進制檔案。你可以指定命令列選項來改變 cargo test 的預設行為。舉例來說 cargo test 預設行為產生的二進制執行檔會平行執行所有測試並獲取測試執行時產生的輸出,讓測試各自的輸出結果不會顯示出來,以更容易讀取相關測試的結果。

有些命令列選項用於 cargo test 而有些則用於產生的測試二進制檔案。要分開這兩種引數,你可以先寫奧用於 cargo test 的引數然後加上 -- 分隔線來區隔要用於測試二進制檔案的引數。執行 cargo test --help 可以顯示你能用在 cargo test 的選項,而執行 cargo test -- --help 在則會顯示你在 -- 之後能用的選項。

平行或接續執行測試

當你執行數個測試時,它們預設會使用執行緒(thread)來平行執行。這樣測試可以更快完成,讓你可以從你或其他人的程式碼更快獲得回饋。因為測試是同時一起執行的,請確保你的測試並不依賴其他測試或是共享的狀態。這包含共享環境,像是目前的工作目錄或是環境變數。

舉例來說,假設你的每個測試都會執行些程式碼會在硬碟上產生一個檔案叫做 test-output.txt 並將一些資料寫入檔案中。然後每個測試讀取檔案中的資料,並判定該檔案有沒有包含特定的值,而這個值在每個測試都不相同。因為測試同時執行,其中的測試可以能覆蓋其他測試寫入與讀取的內容。這樣其他測試就會失敗,並不是因為程式碼不正確,而是因為平行執行時該測試會被其他測試所影響。其中一個解決辦法是確保每個測試都寫入不同的檔案,或者也可以選擇一次只執行一個測試。

如果你不想平行執行測試,或者你想要能更加掌控使用的執行緒數量,你可以傳遞 --test-threads 的選項以及你希望在測試執行檔使用的執行緒數量。請看一下以下範例:

$ cargo test -- --test-threads=1

我們將測試執行緒設為 1,告訴程式不要做任何平行化。使用一條執行緒執行測試會比平行執行它們還來的久,但是如果測試有共享狀態的話,它們就會不互相影響到對方了。

顯示函式輸出結果

如果測試通過的話,Rust 的測試函式庫預設會獲取所有印出的標準輸出。舉例來說,如果我們在測試中呼叫 println! 然後測試通過的話,我們不會在終端機看到 println! 的輸出,我們只會看到一行表達測試通過的訊息。如果測試失敗,我們才會看到所有印出的標準輸出與失敗訊息。

舉例來說,範例 11-10 有個蠢蠢的函式只會印出它的參數並回傳 10,以及一個會通過的測試與一個會失敗的測試。

檔案名稱:src/lib.rs

fn main() {}

fn prints_and_returns_10(a: i32) -> i32 {
    println!("我得到的數值為 {}", a);
    10
}

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

    #[test]
    fn this_test_will_pass() {
        let value = prints_and_returns_10(4);
        assert_eq!(10, value);
    }

    #[test]
    fn this_test_will_fail() {
        let value = prints_and_returns_10(8);
        assert_eq!(5, value);
    }
}

範例 11-10:測試會呼叫 println! 的函式

當我們使用 cargo test 執行這些程式時,我們會看到以下輸出結果:

$ cargo test
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished test [unoptimized + debuginfo] target(s) in 0.58s
     Running target/debug/deps/silly_function-160869f38cff9166

running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok

failures:

---- tests::this_test_will_fail stdout ----
我得到的數值為 8
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `5`,
 right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.


failures:
    tests::this_test_will_fail

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

error: test failed, to rerun pass '--lib'

注意到此輸出結果我們看不到 我得到的數值為 4,這是當測試通過時印出的訊息。這個輸出被獲取走了。而測試會失敗的標準輸出 我得到的數值為 8 則會出現在測試總結輸出的段落上,並同時顯示錯誤發生的原因。

如果我們希望在測試通過時也能看到印出的數值,我們可以用 --show-output 告訴 Rust 也在成功的測試顯示輸出結果。

$ cargo test -- --show-output

當我們使用 --show-output 再次執行範例 11-10 的話,我們就能看到以下輸出:

$ cargo test -- --show-output
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished test [unoptimized + debuginfo] target(s) in 0.60s
     Running target/debug/deps/silly_function-160869f38cff9166

running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok

successes:

---- tests::this_test_will_pass stdout ----
我得到的數值為 4


successes:
    tests::this_test_will_pass

failures:

---- tests::this_test_will_fail stdout ----
我得到的數值為 8
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `5`,
 right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.


failures:
    tests::this_test_will_fail

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

error: test failed, to rerun pass '--lib'

透過名稱來執行部分測試

有時執行完整所有的測試會很花時間。如果你正專注於程式碼的特定部分,你可能會想要只執行與該程式碼有關的測試。你可以向 cargo test 傳遞你想要執行的測試名稱作為引數。

為了解釋如何執行部分測試,我們將為 add_two 函式建立三個測試,如範例 11-11 所示,然後選擇其中一個執行。

檔案名稱:src/lib.rs


#![allow(unused)]
fn main() {
pub fn add_two(a: i32) -> i32 {
    a + 2
}

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

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

    #[test]
    fn add_three_and_two() {
        assert_eq!(5, add_two(3));
    }

    #[test]
    fn one_hundred() {
        assert_eq!(102, add_two(100));
    }
}
}

範例 11-11:三個名稱不同的測試

如果我們沒有傳遞任何引數來執行測試的話,如我們前面看過的一樣,所有測試會平行執行:

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.62s
     Running target/debug/deps/adder-92948b65e88960b4

running 3 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok
test tests::one_hundred ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests adder

running 0 tests

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

執行單獨一個測試

我們可以傳遞任何測試函式的名稱給 cargo test 來只執行該測試:

$ cargo test one_hundred
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.69s
     Running target/debug/deps/adder-92948b65e88960b4

running 1 test
test tests::one_hundred ... ok

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

只有名稱為 one_hundred 的測試會執行,其他兩個的名稱並不符合。測試輸出會在總結的最後顯示 2 filtered out 告訴我們除了命令列執行的測試以外,還有更多其他測試。

我們無法用此方式指定多個測試名稱,只有第一個傳給 cargo test 有用。但我們有其他方式能執行數個測試。

過濾執行數個測試

我們可以指定部分測試名稱,然後任何測試名稱中有相符的就會被執行。舉例來說,因為我們有兩個測試的名稱都包含 add,我們可以透過執行 cargo test add 來執行這兩個測試:

$ cargo test add
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.61s
     Running target/debug/deps/adder-92948b65e88960b4

running 2 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok

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

此命令會執行所有名稱中包含 add 的測試,並過濾掉 one_hundred 的測試名稱。另外測試所在的模組也屬於測試名稱中,所以我們可以透過過濾模組名稱來執行該模組的所有測試。

忽略某些測試除非特別指定

有時候有些特定的測試執行會花非常多時間,所以你可能希望在執行 cargo test 時能排除它們。與其列出所有你想要的測試作為引數,你可以在花時間的測試前加上 ignore 屬性詮釋來排除它們,如以下所示:

檔案名稱:src/lib.rs

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

#[test]
#[ignore]
fn expensive_test() {
    // 會執行一小時的程式碼
}

fn main() {}

對於想排除的測試,我們在 #[test] 之後我們加上 #[ignore]。現在當我們執行我們的測試時,it_works 會執行但 expensive_test 就不會:

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.60s
     Running target/debug/deps/adder-92948b65e88960b4

running 2 tests
test expensive_test ... ignored
test it_works ... ok

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

   Doc-tests adder

running 0 tests

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

expensive_test 函式會列在 ignored,如果我們希望只執行被忽略的測試,我們可以使用 cargo test -- --ignored

$ cargo test -- --ignored
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.61s
     Running target/debug/deps/adder-92948b65e88960b4

running 1 test
test expensive_test ... ok

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

   Doc-tests adder

running 0 tests

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

透過控制哪些測試能執行,你能夠確保快速執行 cargo test。當你有時間能夠執行 ignored 的測試時,你可以執行 cargo test -- --ignored 來等待結果。