ch11-02-running-tests.md
commit 1721a106f78c037ca3074d9c2d5a8cf9c9852cf7
就像 cargo run
會編譯代碼并運(yùn)行生成的二進(jìn)制文件一樣,cargo test
在測試模式下編譯代碼并運(yùn)行生成的測試二進(jìn)制文件??梢灾付钚袇?shù)來改變 cargo test
的默認(rèn)行為。例如,cargo test
生成的二進(jìn)制文件的默認(rèn)行為是并行的運(yùn)行所有測試,并截獲測試運(yùn)行過程中產(chǎn)生的輸出,阻止他們被顯示出來,使得閱讀測試結(jié)果相關(guān)的內(nèi)容變得更容易。可以將一部分命令行參數(shù)傳遞給 cargo test
,而將另外一部分傳遞給生成的測試二進(jìn)制文件。為了分隔這兩種參數(shù),需要先列出傳遞給 cargo test
的參數(shù),接著是分隔符 --
,再之后是傳遞給測試二進(jìn)制文件的參數(shù)。運(yùn)行 cargo test --help
會提示 cargo test
的有關(guān)參數(shù),而運(yùn)行 cargo test -- --help
可以提示在分隔符 --
之后使用的有關(guān)參數(shù)。
當(dāng)運(yùn)行多個測試時, Rust 默認(rèn)使用線程來并行運(yùn)行。這意味著測試會更快地運(yùn)行完畢,所以你可以更快的得到代碼能否工作的反饋。因?yàn)闇y試是在同時運(yùn)行的,你應(yīng)該確保測試不能相互依賴,或依賴任何共享的狀態(tài),包括依賴共享的環(huán)境,比如當(dāng)前工作目錄或者環(huán)境變量。
舉個例子,每一個測試都運(yùn)行一些代碼,假設(shè)這些代碼都在硬盤上創(chuàng)建一個 test-output.txt 文件并寫入一些數(shù)據(jù)。接著每一個測試都讀取文件中的數(shù)據(jù)并斷言這個文件包含特定的值,而這個值在每個測試中都是不同的。因?yàn)樗袦y試都是同時運(yùn)行的,一個測試可能會在另一個測試讀寫文件過程中修改了文件。那么第二個測試就會失敗,并不是因?yàn)榇a不正確,而是因?yàn)闇y試并行運(yùn)行時相互干擾。一個解決方案是使每一個測試讀寫不同的文件;另一個解決方案是一次運(yùn)行一個測試。
如果你不希望測試并行運(yùn)行,或者想要更加精確的控制線程的數(shù)量,可以傳遞 --test-threads
參數(shù)和希望使用線程的數(shù)量給測試二進(jìn)制文件。例如:
$ cargo test -- --test-threads=1
這里將測試線程設(shè)置為 1
,告訴程序不要使用任何并行機(jī)制。這也會比并行運(yùn)行花費(fèi)更多時間,不過在有共享的狀態(tài)時,測試就不會潛在的相互干擾了。
默認(rèn)情況下,當(dāng)測試通過時,Rust 的測試庫會截獲打印到標(biāo)準(zhǔn)輸出的所有內(nèi)容。比如在測試中調(diào)用了 println!
而測試通過了,我們將不會在終端看到 println!
的輸出:只會看到說明測試通過的提示行。如果測試失敗了,則會看到所有標(biāo)準(zhǔn)輸出和其他錯誤信息。
例如,示例 11-10 有一個無意義的函數(shù),它打印出其參數(shù)的值并接著返回 10。接著還有一個會通過的測試和一個會失敗的測試:
文件名: src/lib.rs
fn prints_and_returns_10(a: i32) -> i32 {
println!("I got the value {}", 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:一個調(diào)用了 println!
的函數(shù)的測試
運(yùn)行 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 (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 ----
I got the value 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'
注意輸出中不會出現(xiàn)測試通過時打印的內(nèi)容,即 I got the value 4
。因?yàn)楫?dāng)測試通過時,這些輸出會被截獲。失敗測試的輸出 I got the value 8
,則出現(xiàn)在輸出的測試摘要部分,同時也顯示了測試失敗的原因。
如果你希望也能看到通過的測試中打印的值,也可以在結(jié)尾加上 --show-output
告訴 Rust 顯示成功測試的輸出。
$ cargo test -- --show-output
使用 --show-output
參數(shù)再次運(yùn)行示例 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 (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 ----
I got the value 4
successes:
tests::this_test_will_pass
failures:
---- tests::this_test_will_fail stdout ----
I got the value 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'
有時運(yùn)行整個測試集會耗費(fèi)很長時間。如果你負(fù)責(zé)特定位置的代碼,你可能會希望只運(yùn)行與這些代碼相關(guān)的測試。你可以向 cargo test
傳遞所希望運(yùn)行的測試名稱的參數(shù)來選擇運(yùn)行哪些測試。
為了展示如何運(yùn)行部分測試,示例 11-11 為 add_two
函數(shù)創(chuàng)建了三個測試,我們可以選擇具體運(yùn)行哪一個:
文件名: 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:不同名稱的三個測試
如果沒有傳遞任何參數(shù)就運(yùn)行測試,如你所見,所有測試都會并行運(yùn)行:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.62s
Running unittests (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
傳遞任意測試的名稱來只運(yùn)行這個測試:
$ cargo test one_hundred
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.69s
Running unittests (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
的測試被運(yùn)行了;因?yàn)槠溆鄡蓚€測試并不匹配這個名稱。測試輸出在摘要行的結(jié)尾顯示了 2 filtered out
表明還存在比本次所運(yùn)行的測試更多的測試被過濾掉了。
不能像這樣指定多個測試名稱;只有傳遞給 cargo test
的第一個值才會被使用。不過有運(yùn)行多個測試的方法。
我們可以指定部分測試的名稱,任何名稱匹配這個名稱的測試會被運(yùn)行。例如,因?yàn)轭^兩個測試的名稱包含 add
,可以通過 cargo test add
來運(yùn)行這兩個測試:
$ cargo test add
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running unittests (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
這運(yùn)行了所有名字中帶有 add
的測試,也過濾掉了名為 one_hundred
的測試。同時注意測試所在的模塊也是測試名稱的一部分,所以可以通過模塊名來運(yùn)行一個模塊中的所有測試。
有時一些特定的測試執(zhí)行起來是非常耗費(fèi)時間的,所以在大多數(shù)運(yùn)行 cargo test
的時候希望能排除他們。雖然可以通過參數(shù)列舉出所有希望運(yùn)行的測試來做到,也可以使用 ignore
屬性來標(biāo)記耗時的測試并排除他們,如下所示:
文件名: src/lib.rs
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[test]
#[ignore]
fn expensive_test() {
// 需要運(yùn)行一個小時的代碼
}
對于想要排除的測試,我們在 #[test]
之后增加了 #[ignore]
行?,F(xiàn)在如果運(yùn)行測試,就會發(fā)現(xiàn) it_works
運(yùn)行了,而 expensive_test
沒有運(yùn)行:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.60s
Running unittests (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.00s
expensive_test
被列為 ignored
,如果我們只希望運(yùn)行被忽略的測試,可以使用 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 (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
通過控制運(yùn)行哪些測試,你可以確保能夠快速地運(yùn)行 cargo test
。當(dāng)你需要運(yùn)行 ignored
的測試時,可以執(zhí)行 cargo test -- --ignored
。如果你希望不管是否忽略都要運(yùn)行全部測試,可以運(yùn)行 cargo test -- --include-ignored
。
更多建議: