Управление запуском тестов
Как 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, а также один проходящий тест и один проваливающийся.
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);
}
}
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, и будем выбирать, какие из них
запускать.
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 и тем самым исключить их:
Файл: 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.