Керування тим, як запускаються тести
Так само як cargo run компілює ваш код, а потім запускає отриманий двійковий файл,
cargo test компілює ваш код у режимі тестування і запускає отриманий тестовий
двійковий файл. Поведінка за замовчуванням двійкового файлу, створеного cargo test, полягає в тому, щоб запускати
всі тести паралельно й перехоплювати виведення, згенероване під час запуску тестів,
не дозволяючи відображати це виведення і полегшуючи читання
виведення, пов’язаного з результатами тестів. Однак ви можете вказати параметри командного рядка,
щоб змінити цю поведінку за замовчуванням.
Деякі параметри командного рядка передаються до cargo test, а деякі — до отриманого тестового
двійкового файлу. Щоб відокремити ці два типи аргументів, ви перелічуєте аргументи, які
передаються до cargo test, за якими слідує роздільник --, а потім ті, що
передаються до тестового двійкового файлу. Запуск cargo test --help показує параметри, які ви можете використовувати
з cargo test, а запуск cargo test -- --help показує параметри, які ви
можете використовувати після роздільника. Ці параметри також документовані в Розділі “Tests”
The rustc Book.
Запуск тестів паралельно або послідовно
Коли ви запускаєте кілька тестів, за замовчуванням вони запускаються паралельно, використовуючи потоки, тобто вони завершуються швидше, і ви отримуєте відгук раніше. Оскільки тести виконуються одночасно, ви маєте переконатися, що ваші тести не залежать один від одного або від будь-якого спільного стану, включно зі спільним середовищем, таким як поточний робочий каталог або змінні середовища.
Наприклад, припустімо, що кожен із ваших тестів виконує деякий код, який створює файл на диску з іменем test-output.txt і записує деякі дані в цей файл. Потім кожен тест читає дані в цьому файлі і стверджує, що файл містить певне значення, яке в кожному тесті різне. Оскільки тести виконуються одночасно, один тест може перезаписати файл у проміжку між тим, як інший тест записує і читає файл. Тоді другий тест завершиться помилкою не тому, що код неправильний, а тому, що тести завадили один одному під час паралельного виконання. Одне з рішень — переконатися, що кожен тест записує в окремий файл; інше рішення — запускати тести по одному.
Якщо ви не хочете запускати тести паралельно або якщо ви хочете більш детального
контролю над кількістю потоків, що використовуються, ви можете передати прапорець --test-threads
і кількість потоків, яку ви хочете використовувати, тестовому двійковому файлу. Подивіться на
наступний приклад:
$ cargo test -- --test-threads=1
Ми встановлюємо кількість тестових потоків на 1, кажучи програмі не використовувати ніякої
паралельності. Запуск тестів з використанням одного потоку займе більше часу, ніж запуск
їх паралельно, але тести не заважатимуть один одному, якщо вони мають спільний
стан.
Показ виведення функції
За замовчуванням, якщо тест успішний, тестова бібліотека Rust перехоплює все, що надруковано до
стандартного виведення. Наприклад, якщо ми викликаємо println! у тесті і тест
успішний, ми не побачимо виведення println! у терміналі; ми побачимо лише
рядок, який вказує, що тест успішний. Якщо тест завершується помилкою, ми побачимо все, що було
надруковано до стандартного виведення, разом з рештою повідомлення про помилку.
Як приклад, Listing 11-10 має безглузду функцію, яка друкує значення свого параметра і повертає 10, а також тест, який успішний, і тест, який завершується помилкою.
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);
}
}
Коли ми запускаємо ці тести за допомогою cargo test, ми побачимо таке виведення:
$ 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`
Зверніть увагу, що ніде в цьому виведенні ми не бачимо I got the value 4, яке
друкується, коли запускається тест, що успішний. Це виведення було перехоплено. Виведення
від тесту, який завершився помилкою, I got the value 8, з’являється в розділі
підсумкового виведення тестів, який також показує причину невдачі тесту.
Якщо ми хочемо бачити надруковані значення також і для успішних тестів, ми можемо сказати Rust
також показувати виведення успішних тестів за допомогою --show-output:
$ cargo test -- --show-output
Коли ми знову запускаємо тести в Listing 11-10 з прапорцем --show-output, ми
бачимо таке виведення:
$ 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`
Запуск підмножини тестів за назвою
Запуск повного набору тестів іноді може займати багато часу. Якщо ви працюєте над
кодом у певній області, ви можете захотіти запускати лише тести, що стосуються
цього коду. Ви можете вибрати, які тести запускати, передавши cargo test назву
або назви тесту(ів), які ви хочете запустити, як аргумент.
Щоб продемонструвати, як запускати підмножину тестів, спочатку ми створимо три тести для
нашої функції add_two, як показано в Listing 11-11, і виберемо, які з них запускати.
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);
}
}
Якщо ми запускаємо тести без передавання будь-яких аргументів, як ми бачили раніше, усі тести будуть запускатися паралельно:
$ 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
Запуск окремих тестів
Ми можемо передати назву будь-якої тестової функції до cargo 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
Запустився лише тест із назвою one_hundred; інші два тести не відповідали
цій назві. Виведення тестів дає нам знати, що в нас було більше тестів, які не запускалися,
показуючи 2 filtered out наприкінці.
Ми не можемо вказати назви кількох тестів таким чином; буде використано лише перше значення,
надане cargo test. Але є спосіб запускати кілька тестів.
Фільтрація для запуску кількох тестів
Ми можемо вказати частину назви тесту, і буде запущено будь-який тест, назва якого відповідає цьому значенню.
Наприклад, оскільки назви двох наших тестів містять add, ми можемо
запустити ці два, виконавши 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
Ця команда запустила всі тести з add у назві і відфільтрувала тест,
названий one_hundred. Також зверніть увагу, що модуль, у якому з’являється тест, стає
частиною назви тесту, тож ми можемо запустити всі тести в модулі, відфільтрувавши
за назвою модуля.
Ігнорування тестів, якщо їх явно не запрошено
Іноді виконання кількох конкретних тестів може займати дуже багато часу, тож ви
можете захотіти виключити їх під час більшості запусків cargo test. Замість того щоб
перелічувати як аргументи всі тести, які ви хочете запускати, ви можете натомість анотувати
довготривалі тести, використовуючи атрибут ignore, щоб виключити їх, як показано
тут:
Filename: 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
}
}
Після #[test] ми додаємо рядок #[ignore] до тесту, який хочемо виключити.
Тепер, коли ми запускаємо наші тести, it_works запускається, але expensive_test — ні:
$ 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
Функція expensive_test перерахована як ignored. Якщо ми хочемо запускати лише
ігноровані тести, ми можемо використати 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
Керуючи тим, які тести запускаються, ви можете переконатися, що результати вашого cargo test
повертатимуться швидко. Коли ви дійдете до моменту, коли має сенс перевірити
результати ignored тестів і у вас є час чекати на результати,
ви можете натомість запустити cargo test -- --ignored. Якщо ви хочете запустити всі тести
незалежно від того, чи вони ігноровані, ви можете запустити cargo test -- --include-ignored.