Управление запуском тестов
Так же как 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' 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, а затем выберем, какие
из них запускать.
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() {
// 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.