Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Управление выполнением тестов

Так же как cargo run компилирует ваш код и затем запускает полученный бинарник, cargo test компилирует код в режиме тестирования и запускает полученный тестовый бинарник. По умолчанию бинарник, созданный cargo test, выполняет все тесты параллельно и перехватывает вывод, генерируемый во время выполнения тестов, не позволяя ему отображаться. Это облегчает чтение вывода, связанного с результатами тестов. Однако вы можете указать параметры командной строки, чтобы изменить это поведение.

Некоторые параметры командной строки передаются cargo test, а некоторые — полученному тестовому бинарнику. Чтобы разделить эти два типа аргументов, укажите аргументы для cargo test, затем разделитель --, а после — аргументы для тестового бинарника. Запуск cargo test --help отображает опции, которые можно использовать с cargo test, а cargo test -- --help — опции после разделителя. Эти опции также документированы в разделе “Tests” книги rustc.

Параллельное или последовательное выполнение тестов

При запуске нескольких тестов по умолчанию они выполняются параллельно с использованием потоков, что означает более быстрое завершение и быструю обратную связь. Поскольку тесты работают одновременно, вы должны убедиться, что ваши тесты не зависят друг от друга или от любого общего состояния, включая общее окружение, такое как текущий рабочий каталог или переменные окружения.

Например, предположим, что каждый из ваших тестов выполняет код, который создаёт на диске файл с именем test-output.txt и записывает в него некоторые данные. Затем каждый тест читает данные из этого файла и проверяет, что файл содержит определённое значение, которое различается в каждом тесте. Поскольку тесты выполняются одновременно, один тест может перезаписать файл в промежутке между записью и чтением файла другим тестом. Второй тест тогда завершится с ошибкой, не потому что код некорректен, а потому что тесты вмешались друг в друга при параллельном выполнении. Одно решение — убедиться, что каждый тест записывает в разные файлы; другое решение — запускать тесты по одному.

Если вы не хотите запускать тесты параллельно или хотите более тонко контролировать количество используемых потоков, вы можете передать тестовому бинарнику флаг --test-threads и количество потоков, которое хотите использовать. Посмотрите на следующий пример:

$ cargo test -- --test-threads=1

Мы устанавливаем количество тестовых потоков в 1, указывая программе не использовать параллелизм. Запуск тестов с одним потоком займёт больше времени, чем параллельный запуск, но тесты не будут мешать друг другу, если они используют общее состояние.

Отображение вывода функции

По умолчанию, если тест проходит, тестовая библиотека Rust перехватывает весь вывод, направляемый в стандартный поток вывода. Например, если мы вызываем println! в тесте и тест проходит, мы не увидим вывод println! в терминале; мы увидим только строку, указывающую, что тест прошёл. Если тест завершается с ошибкой, мы увидим весь вывод, направленный в стандартный поток вывода, вместе с сообщением об ошибке.

Например, в Листинге 11-10 показана несерьёзная функция, которая выводит значение своего параметра и возвращает 10, а также тест, который проходит, и тест, который завершается с ошибкой.

Filename: 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!(value, 10);
    }

    #[test]
    fn this_test_will_fail() {
        let value = prints_and_returns_10(8);
        assert_eq!(value, 5);
    }
}
Listing 11-10: Тесты для функции, вызывающей println!

Когда мы запускаем эти тесты с помощью 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

Когда мы снова запускаем тесты из Листинга 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, как показано в Листинге 11-11, и выберем, какие из них запустить.

Filename: src/lib.rs
pub fn add_two(a: usize) -> usize {
    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);
    }
}
Listing 11-11: Три теста с тремя разными именами

Если мы запустим тесты без передачи аргументов, как мы видели ранее, все тесты будут выполняться параллельно:

$ 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, чтобы исключить их, как показано здесь:

Имя файла: 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 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.