Параллельные тесты
Параллельные тесты - это режим выполнения тестов, который
используется в Rust по умолчанию. Когда вы запускаете
cargo test, фреймворк тестирования автоматически
распределяет тесты по нескольким потокам и выполняет их
одновременно. Это значительно ускоряет процесс проверки,
особенно при большом количестве тестов. Параллельный запуск
не требует специального синтаксиса - достаточно объявить
тестовые функции с атрибутом #[test], и они будут
выполняться параллельно без дополнительной настройки.
Стоит учитывать, что при параллельном выполнении тестов
порядок их запуска не гарантирован. Если тесты используют
общие ресурсы (файлы, переменные окружения, базы данных),
это может привести к конфликтам. Для управления
параллелизмом используется флаг --test-threads,
который позволяет задать количество потоков или вовсе
отключить параллельный режим.
Синтаксис
// Параллельный запуск по умолчанию
cargo test
// Указать количество потоков
cargo test -- --test-threads=4
// Отключить параллелизм (один поток)
cargo test -- --test-threads=1
Пример
Давайте создадим модуль с тестами, которые будут выполняться параллельно:
fn add(a: i32, b: i32) -> i32
{
a + b
}
fn multiply(a: i32, b: i32) -> i32
{
a * b
}
#[cfg(test)]
mod tests
{
use super::*;
#[test]
fn test_add()
{
let res = add(2, 3);
assert_eq!(res, 5);
}
#[test]
fn test_multiply()
{
let res = multiply(4, 5);
assert_eq!(res, 20);
}
#[test]
fn test_add_negative()
{
let res = add(-2, -3);
assert_eq!(res, -5);
}
}
fn main()
{
println!("{}", add(2, 3));
println!("{}", multiply(4, 5));
}
При запуске cargo test все три теста
(test_add, test_multiply,
test_add_negative) будут выполнены параллельно
в разных потоках. Результат выполнения:
running 3 tests
test tests::test_add ... ok
test tests::test_multiply ... ok
test tests::test_add_negative ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Пример
Давайте запустим тесты в одном потоке, чтобы убедиться в последовательном выполнении:
fn compute(x: i32) -> i32
{
x * x
}
#[cfg(test)]
mod tests
{
use super::*;
#[test]
fn test_one()
{
let res = compute(1);
assert_eq!(res, 1);
}
#[test]
fn test_two()
{
let res = compute(2);
assert_eq!(res, 4);
}
#[test]
fn test_three()
{
let res = compute(3);
assert_eq!(res, 9);
}
}
fn main()
{
println!("{}", compute(5));
}
Команда для запуска в одном потоке:
cargo test -- --test-threads=1
Результат выполнения кода:
running 3 tests
test tests::test_one ... ok
test tests::test_two ... ok
test tests::test_three ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Пример
Давайте рассмотрим пример, где параллельный запуск может вызвать проблему с общими данными. Такой тест следует запускать последовательно:
use std::sync::Mutex;
static COUNTER: Mutex<i32> = Mutex::new(0);
fn increment() -> i32
{
let mut val = COUNTER.lock().unwrap();
*val += 1;
*val
}
#[cfg(test)]
mod tests
{
use super::*;
#[test]
fn test_increment_first()
{
let res = increment();
assert!(res >= 1);
}
#[test]
fn test_increment_second()
{
let res = increment();
assert!(res >= 1);
}
}
fn main()
{
println!("{}", increment());
}
Если такие тесты зависят от порядка выполнения,
их нужно запускать с флагом --test-threads=1,
чтобы избежать состояния гонки. Результат
последовательного выполнения:
running 2 tests
test tests::test_increment_first ... ok
test tests::test_increment_second ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Смотрите также
-
атрибут
test,
который помечает функцию как тестовую -
команда
cargo test,
которая запускает все тесты в проекте -
флаг
filter,
который позволяет запускать только выбранные тесты -
опция
show-output,
которая показывает вывод println в тестах