控制程式如何執行

就像 cargo run 會編譯你的程式碼並執行產生的執行檔,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 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 unittests src/lib.rs (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; finished in 0.00s

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 unittests src/lib.rs (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; finished in 0.00s

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

透過名稱來執行部分測試

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

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

檔案名稱:src/lib.rs

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 unittests src/lib.rs (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; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

執行單獨一個測試

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

$ cargo test one_hundred
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.69s
     Running unittests src/lib.rs (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; finished in 0.00s

只有名稱為 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 unittests src/lib.rs (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; finished in 0.00s

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

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

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

檔案名稱:src/lib.rs

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

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

對於想排除的測試,我們在 #[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 unittests src/lib.rs (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; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s

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 unittests src/lib.rs (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; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

透過控制哪些測試能執行,你能夠確保快速執行 cargo test。當你有時間能夠執行 ignored 的測試時,你可以執行 cargo test -- --ignored 來等待結果。如果你想執行所有程式,無論他們是不是被忽略的話,你可以執行 cargo test -- --include-ignored