Тестирование в Rust: фреймворки и инструменты
Rust — язык системного программирования, который уделяет огромное внимание надёжности и безопасности кода. Встроенная система тестирования позволяет разработчикам писать unit-тесты, интеграционные тесты и документационные тесты без установки дополнительных библиотек. Однако для сложных сценариев — параметризации, генерации случайных данных или мокирования — сообщество Rust создало мощные фреймворки. В этой статье мы разберём ключевые инструменты тестирования в Rust.
Встроенный модуль test
Rust поставляется со встроенным модулем test, который активируется через атрибут #[test]. Для запуска тестов используется команда cargo test. Пример простого unit-теста:
// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 2), 4);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, 1), 0);
}
}
Ключевые возможности встроенного модуля:
- Атрибуты:
#[test],#[ignore],#[should_panic]. - Макросы утверждений:
assert!,assert_eq!,assert_ne!. - Интеграционные тесты: размещаются в папке
tests/и тестируют публичный API. - Документационные тесты: примеры кода в doc-комментариях, проверяемые через
cargo test.
Для большинства проектов этого достаточно. Но когда требуется параметризация, генерация случайных данных или моки, на помощь приходят специализированные фреймворки.
Фреймворк rstest — параметризованные тесты
Библиотека rstest предоставляет мощный механизм параметризации тестов, аналогичный pytest в Python. Она позволяет задавать несколько наборов входных данных и ожидаемых результатов без дублирования кода.
Установка:
[dev-dependencies]
rstest = "0.22"
Пример параметризованного теста:
use rstest::rstest;
#[rstest]
#[case(1, 2, 3)]
#[case(0, 0, 0)]
#[case(-1, 1, 0)]
fn test_add(#[case] a: i32, #[case] b: i32, #[case] expected: i32) {
assert_eq!(add(a, b), expected);
}
Возможности rstest:
- Параметризация через
#[case]. - Генерация случайных данных через
#[random]. - Контекстные фикстуры через
#[fixture]. - Поддержка асинхронных тестов.
Этот фреймворк особенно полезен при тестировании математических функций, парсеров или алгоритмов, где нужно проверить множество граничных случаев.
Property-based testing с proptest
Property-based testing (тестирование на основе свойств) — метод, при котором вы описываете инварианты (свойства), которые должны выполняться для всех возможных входных данных. Библиотека proptest генерирует случайные входные данные и проверяет эти свойства.
Установка:
[dev-dependencies]
proptest = "1.5"
Пример: проверка, что сортировка сохраняет длину и все элементы:
use proptest::prelude::*;
proptest! {
#[test]
fn test_sort_keeps_elements(mut vec: Vec<i32>) {
let original_len = vec.len();
let mut sorted = vec.clone();
sorted.sort();
assert_eq!(sorted.len(), original_len);
for item in &vec {
assert!(sorted.contains(item));
}
}
}
Преимущества proptest:
- Автоматическая генерация тысяч случайных сценариев.
- Минимизация (shrink) — при падении теста библиотека находит минимальный пример, вызывающий ошибку.
- Поддержка сложных структур данных через комбинаторы.
- Интеграция с
rstestи другими фреймворками.
Property-based testing незаменим для тестирования парсеров, сериализации, криптографических функций и любых алгоритмов, где сложно перебрать все возможные случаи вручную.