Как создавать параметризованные тесты в Rust для проверки множества входных данных?
Параметризованные тесты в Rust: встроенные методы и крейты
В Rust нет встроенного атрибута для параметризованных тестов (как `@pytest.mark.parametrize` в Python). Однако есть несколько эффективных способов реализовать эту функциональность. Рассмотрим лучшие подходы.
1. Использование крейта rstest (рекомендуется)
Крейт rstest — самое популярное и удобное решение. Он предоставляет атрибут #[rstest] с поддержкой параметризации. Добавьте в Cargo.toml:
[dev-dependencies]
rstest = "0.18"
Пример использования:
use rstest::rstest;
#[rstest]
#[case(0, 1)]
#[case(1, 1)]
#[case(5, 120)]
#[case(10, 3628800)]
fn test_factorial(#[case] input: u64, #[case] expected: u64) {
assert_eq!(factorial(input), expected);
}
Если нужно много кейсов, можно использовать списки:
use rstest::rstest;
#[rstest]
#[case::zero(0, 1)]
#[case::one(1, 1)]
#[case::five(5, 120)]
#[case::ten(10, 3628800)]
fn test_factorial_named(#[case] input: u64, #[case] expected: u64) {
assert_eq!(factorial(input), expected);
}
Для генерации тестов из файлов или конфигураций используйте #[rstest(case(...))] с динамическими данными.
2. Использование макроса test_case из крейта test-case
Альтернативный популярный крейт:
[dev-dependencies]
test-case = "3.1"
use test_case::test_case;
#[test_case(0, 1)]
#[test_case(1, 1)]
#[test_case(5, 120)]
#[test_case(10, 3628800)]
fn test_factorial(input: u64, expected: u64) {
assert_eq!(factorial(input), expected);
}
Крейт test-case поддерживает вложенные тесты, игнорирование отдельных кейсов и генерацию описаний.
3. Использование табличных тестов (без внешних крейтов)
Если вы хотите избежать зависимостей, можно использовать макрос macro_rules! или просто итерацию:
#[test]
fn test_factorial_table() {
let test_cases = vec![
(0, 1),
(1, 1),
(5, 120),
(10, 3628800),
(7, 5040),
];
for (input, expected) in test_cases {
assert_eq!(
factorial(input),
expected,
"Тест не пройден для input = {}",
input
);
}
}
Недостаток: при первом же провале тест остановится, и вы не увидите результаты для остальных кейсов. Для лучшего отчёта можно использовать rstest или test-case — они запускают каждый кейс как отдельный тест.
Советы по организации
- Используйте
rstest— он наиболее гибкий и популярный (более 5 млн загрузок). - Давайте осмысленные имена кейсам с помощью
#[case::имя(...)]. - Для больших наборов данных можно вынести кейсы в константу или читать из файла.
- Комбинируйте с другими атрибутами — например,
#[ignore]или#[should_panic].
Пример с комбинированными параметрами:
#[rstest]
#[case(0, 1)]
#[case(1, 1)]
#[should_panic]
#[case(100, 0)] // ожидаем панику
fn test_factorial_edge(#[case] input: u64, #[case] expected: u64) {
assert_eq!(factorial(input), expected);
}
Итог
Рекомендую начать с rstest — он простой, мощный и хорошо документирован. Если проект требует минимум зависимостей — используйте табличный подход. В любом случае, параметризованные тесты значительно упрощают поддержку кода и повышают покрытие.
Альтернативный подход: макросы для параметризованных тестов
Если вы хотите полностью избежать внешних крейтов, можно написать собственный макрос. Это даёт полный контроль и не добавляет зависимостей. Пример простого макроса для параметризации:
macro_rules! parameterized_test {
($name:ident, $cases:expr) => {
#[test]
fn $name() {
for (input, expected) in $cases {
assert_eq!(
factorial(input),
expected,
"Failed for input: {}",
input
);
}
}
};
}
parameterized_test!(test_factorial_cases, vec![
(0, 1),
(1, 1),
(5, 120),
(10, 3628800),
]);
Можно расширить макрос для поддержки разных типов и кастомных проверок:
macro_rules! param_test {
($name:ident, $test_fn:expr, [$(( $input:expr, $expected:expr )),+]) => {
#[test]
fn $name() {
$(
assert_eq!($test_fn($input), $expected, "Failed for input: {}", $input);
)+
}
};
}
param_test!(test_factorial_macro, factorial, [
(0, 1),
(1, 1),
(5, 120),
(10, 3628800)
]);
Преимущества: нет зависимостей, полный контроль над логикой. Недостатки: сложнее поддерживать, нет разделения на отдельные тесты (при провале одного кейса весь тест падает), меньше возможностей для кастомизации.
Этот подход подходит для небольших проектов или когда вы хотите минимизировать количество крейтов. Для серьёзной разработки всё же рекомендую rstest или test-case.
Ответов пока нет.
Войдите чтобы оставить ответ