Kontrolowanie sposobu uruchamiania testów
Tak jak cargo run kompiluje Twój kod, a następnie uruchamia wynikowy plik binarny, tak cargo test kompiluje Twój kod w trybie testowym i uruchamia wynikowy plik binarny testów. Domyślne zachowanie pliku binarnego wyprodukowanego przez cargo test to uruchamianie wszystkich testów równolegle i przechwytywanie danych wyjściowych generowanych podczas przebiegów testów, co zapobiega wyświetlaniu danych wyjściowych i ułatwia czytanie danych wyjściowych związanych z wynikami testów. Możesz jednak określić opcje wiersza poleceń, aby zmienić to domyślne zachowanie.
Niektóre opcje wiersza poleceń trafiają do cargo test, a niektóre do wynikowego pliku binarnego testów. Aby rozdzielić te dwa typy argumentów, należy wymienić argumenty, które trafiają do cargo test, po których następuje separator --, a następnie te, które trafiają do pliku binarnego testów. Uruchomienie cargo test --help wyświetla opcje, których można użyć z cargo test, a uruchomienie cargo test -- --help wyświetla opcje, których można użyć po separatorze. Opcje te są również udokumentowane w sekcji „Testy” w The rustc Book.
Uruchamianie testów równolegle lub sekwencyjnie
Kiedy uruchamiasz wiele testów, domyślnie działają one równolegle, używając wątków, co oznacza, że kończą się szybciej i otrzymujesz informację zwrotną wcześniej. Ponieważ testy działają jednocześnie, musisz upewnić się, że Twoje testy nie zależą od siebie nawzajem ani od żadnego współdzielonego stanu, w tym współdzielonego środowiska, takiego jak bieżący katalog roboczy lub zmienne środowiskowe.
Na przykład, powiedzmy, że każdy z twoich testów uruchamia pewien kod, który tworzy plik na dysku o nazwie test-output.txt i zapisuje do niego pewne dane. Następnie, każdy test odczytuje dane z tego pliku i twierdzi, że plik zawiera określoną wartość, która jest różna w każdym teście. Ponieważ testy działają jednocześnie, jeden test może nadpisać plik w czasie między zapisem a odczytem pliku przez inny test. Drugi test zakończy się niepowodzeniem, nie dlatego, że kod jest niepoprawny, ale dlatego, że testy wzajemnie się zakłócały podczas działania równoległego. Jednym z rozwiązań jest upewnienie się, że każdy test zapisuje do innego pliku; innym rozwiązaniem jest uruchamianie testów jeden po drugim.
Jeśli nie chcesz uruchamiać testów równolegle lub jeśli chcesz mieć bardziej precyzyjną kontrolę nad liczbą używanych wątków, możesz przekazać flagę --test-threads oraz liczbę wątków, których chcesz użyć, do binarnego pliku testowego. Spójrz na następujący przykład:
$ cargo test -- --test-threads=1
Ustawiamy liczbę wątków testowych na 1, informując program, aby nie używał żadnego paralelizmu. Uruchamianie testów na jednym wątku zajmie więcej czasu niż uruchamianie ich równolegle, ale testy nie będą ze sobą kolidować, jeśli współdzielą stan.
Wyświetlanie wyjścia funkcji
Domyślnie, jeśli test przechodzi, biblioteka testowa Rust przechwytuje wszystko, co jest wypisywane na standardowe wyjście. Na przykład, jeśli wywołamy println! w teście, a test przejdzie, nie zobaczymy wyjścia println! w terminalu; zobaczymy tylko wiersz wskazujący, że test przeszedł. Jeśli test zakończy się niepowodzeniem, zobaczymy wszystko, co zostało wypisane na standardowe wyjście, wraz z resztą komunikatu o błędzie.
Jako przykład, Listing 11-10 zawiera prostą funkcję, która wypisuje wartość swojego parametru i zwraca 10, a także test, który przechodzi, i test, który kończy się niepowodzeniem.
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!(value, 10);
}
#[test]
fn this_test_will_fail() {
let value = prints_and_returns_10(8);
assert_eq!(value, 5);
}
}
Kiedy uruchomimy te testy za pomocą cargo test, zobaczymy następujące dane wyjściowe:
$ cargo test
Compiling silly-function v0.1.0 (file:///projects/silly-function)
Finished `test` profile [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 ----
I got the value 8
thread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:
assertion `left == right` failed
left: 10
right: 5
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`
Zauważ, że w tym wyjściu nigdzie nie widzimy I got the value 4, które jest wypisywane, gdy test, który przechodzi, jest uruchamiany. To wyjście zostało przechwycone. Wyjście z testu, który zakończył się niepowodzeniem, I got the value 8, pojawia się w sekcji podsumowania testów, która również pokazuje przyczynę niepowodzenia testu.
Jeśli chcemy zobaczyć wypisane wartości również dla przechodzących testów, możemy nakazać Rustowi wyświetlanie danych wyjściowych udanych testów za pomocą --show-output:
$ cargo test -- --show-output
Kiedy ponownie uruchomimy testy z Listingu 11-10 z flagą --show-output, zobaczymy następujące dane wyjściowe:
$ cargo test -- --show-output
Compiling silly-function v0.1.0 (file:///projects/silly-function)
Finished `test` profile [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 ----
I got the value 4
successes:
tests::this_test_will_pass
failures:
---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:
assertion `left == right` failed
left: 10
right: 5
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`
Uruchamianie podzbioru testów po nazwie
Uruchamianie pełnego zestawu testów może czasem zająć dużo czasu. Jeśli pracujesz nad kodem w określonym obszarze, możesz chcieć uruchomić tylko testy dotyczące tego kodu. Możesz wybrać, które testy uruchomić, przekazując cargo test nazwę lub nazwy testów, które chcesz uruchomić, jako argument.
Aby zademonstrować, jak uruchomić podzbiór testów, najpierw utworzymy trzy testy dla naszej funkcji add_two, jak pokazano w Listingu 11-11, i wybierzemy, które z nich uruchomić.
pub fn add_two(a: u64) -> u64 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_two_and_two() {
let result = add_two(2);
assert_eq!(result, 4);
}
#[test]
fn add_three_and_two() {
let result = add_two(3);
assert_eq!(result, 5);
}
#[test]
fn one_hundred() {
let result = add_two(100);
assert_eq!(result, 102);
}
}
Jeśli uruchomimy testy bez przekazywania żadnych argumentów, jak widzieliśmy wcześniej, wszystkie testy zostaną uruchomione równolegle:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished `test` profile [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
Uruchamianie pojedynczych testów
Możemy przekazać nazwę dowolnej funkcji testowej do cargo test, aby uruchomić tylko ten test:
$ cargo test one_hundred
Compiling adder v0.1.0 (file:///projects/adder)
Finished `test` profile [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
Uruchomiono tylko test o nazwie one_hundred; pozostałe dwa testy nie pasowały do tej nazwy. Wyniki testów informują nas, że było więcej testów, które nie zostały uruchomione, wyświetlając na końcu 2 filtered out.
W ten sposób nie można określać nazw wielu testów; użyta zostanie tylko pierwsza wartość podana cargo test. Ale istnieje sposób na uruchamianie wielu testów.
Filtrowanie w celu uruchomienia wielu testów
Możemy określić część nazwy testu, a każdy test, którego nazwa pasuje do tej wartości, zostanie uruchomiony. Na przykład, ponieważ nazwy dwóch naszych testów zawierają add, możemy uruchomić te dwa, uruchamiając cargo test add:
$ cargo test add
Compiling adder v0.1.0 (file:///projects/adder)
Finished `test` profile [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
To polecenie uruchomiło wszystkie testy zawierające add w nazwie i odfiltrowało test o nazwie one_hundred. Zwróć również uwagę, że moduł, w którym pojawia się test, staje się częścią nazwy testu, więc możemy uruchomić wszystkie testy w module, filtrując po nazwie modułu.
Ignorowanie testów, chyba że są wyraźnie żądane
Czasami kilka konkretnych testów może być bardzo czasochłonnych w wykonaniu, więc możesz chcieć je wykluczyć podczas większości uruchomień cargo test. Zamiast wymieniać jako argumenty wszystkie testy, które chcesz uruchomić, możesz zamiast tego opatrzyć czasochłonne testy atrybutem ignore, aby je wykluczyć, jak pokazano tutaj:
Nazwa pliku: src/lib.rs
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
#[test]
#[ignore]
fn expensive_test() {
// code that takes an hour to run
}
}
Po #[test] dodajemy wiersz #[ignore] do testu, który chcemy wykluczyć. Teraz, gdy uruchomimy nasze testy, it_works zostanie uruchomione, ale expensive_test nie:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 2 tests
test tests::expensive_test ... ignored
test tests::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
Funkcja expensive_test jest wymieniona jako ignored. Jeśli chcemy uruchomić tylko ignorowane testy, możemy użyć cargo test -- --ignored:
$ cargo test -- --ignored
Compiling adder v0.1.0 (file:///projects/adder)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::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
Kontrolując, które testy są uruchamiane, możesz mieć pewność, że wyniki cargo test zostaną zwrócone szybko. Kiedy nadejdzie moment, w którym sensowne jest sprawdzenie wyników testów ignored i masz czas, aby poczekać na wyniki, możesz zamiast tego uruchomić cargo test -- --ignored. Jeśli chcesz uruchomić wszystkie testy, niezależnie od tego, czy są ignorowane, czy nie, możesz uruchomić cargo test -- --include-ignored.