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» книги The rustc Book.

Параллельный или последовательный запуск

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

Допустим, каждый ваш тест запускает код, который создаёт на диске файл 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!(value, 10);
    }

    #[test]
    fn this_test_will_fail() {
        let value = prints_and_returns_10(8);
        assert_eq!(value, 5);
    }
}
Листинг 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' (6019863) 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' (6022313) 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, и будем выбирать, какие из них запускать.

Файл: src/lib.rs
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);
    }
}
Листинг 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() {
        // код, который выполняется целый час
    }
}

После #[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.