Контроль над запуском тестів

Так само як cargo run компілює ваш код і запускає утворений виконуваний файл, cargo test компілює ваш код у режимі тестів і запускає утворений тестовий виконуваний файл. За замовчанням виконуваний файл, згенерований cargo test, запускає всі тести паралельно і перехоплює вивід, згенерований під час виконання тестів, запобігаючи виведенню на екран і спрощуючи читання виведення, яке стосується результатів тестів. Однак ви можете вказати опції командного рядка, щоб змінити таку поведінку за замовчанням.

Деякі опції командного рядка стосуються cargo test, а деякі - вихідного тестового виконуваного файла. Щоб розділити ці два типи аргументів, треба вказати аргументи, що стосуються cargo test, далі розділювач --, а потім ті, що стосуються тестового виконуваного файлу. Запуск cargo test --help покаже опції, що можна використовувати з cargo test, а запуск cargo test -- --help покаже опції, що можна вказувати після розділювача.

Запуск тестів паралельно чи послідовно

Коли ви запускаєте декілька тестів, за замовчуванням вони запускаються паралельно у кількох потоках, тобто вони закінчують роботу швидше і ви скоріше отримуєте зворотний зв'язок. Оскільки тести виконуються одночасно, ви повинні переконатися, що ваші тести не залежать один від одного або від будь-якого спільного стану, включно зі спільним середовищем, наприклад поточною робочою текою чи змінною середовища.

Наприклад, нехай кожен з ваших тестів запускає певний код, що створює файл на диску з назвою 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!("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: Тести для функції, що викликає 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 ----
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'

Зверніть увагу, що у виведеному ніде немає I got the value 4 - того, що виводиться в тесті, що проходить. Це виведення було перехоплено. Виведення з тесту, що провалився, I got the value 8, з'являється в розділі підсумків тесту, де також показана і причина провалу тесту.

Якщо ми хочемо вивести значення і для тестів, що пройшли, ми можемо сказати Rust також показати виведення з успішних тестів за допомогою --show-output.

$ cargo test -- --show-output

Коли ми запустимо тести з Блоку коду 11-10 знову, вказавши прапорець --show-output, то побачимо таке:

$ 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 ----
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'

Запуск підмножини тестів по імені

Іноді виконання повного набору тестів може тривати довго. Якщо ви працюєте над кодом у певній області, то можете захотіти запускати лише тести, що містять цей код. Ви можете обирати, які тести виконати, передавши 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() {
    // 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 [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.00s

Функція 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.