Тестирование в Rust: фреймворки и инструменты для качественного кода

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Тестирование в Rust: от юнит-тестов до property-based testing

Rust — один из немногих языков, где система типов и владения уже на этапе компиляции исключает целые классы ошибок (гонки данных, висячие указатели, двойное освобождение памяти). Однако логические ошибки, некорректная обработка краевых случаев и неверная бизнес-логика остаются. Для их выявления в экосистеме Rust сложился мощный набор инструментов тестирования — от встроенного тест-раннера до продвинутых фреймворков для генерации тестовых данных.

Встроенные средства: cargo test

Базовый инструмент тестирования встроен прямо в компилятор и менеджер пакетов. Для создания тестов достаточно добавить модуль с атрибутом #[cfg(test)] и пометить функции атрибутом #[test].

// src/lib.rs
pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }

    #[test]
    fn test_add_negative() {
        // u64 не поддерживает отрицательные числа — это ошибка компиляции
        // Но если бы функция принимала i64:
        // assert_eq!(add(-2, 2), 0);
    }
}

Запуск: cargo test. Флаг -- --nocapture выводит println! из тестов. Флаг -- --test-threads=1 отключает параллельный запуск.

Интеграционные тесты

Интеграционные тесты находятся в папке tests/ на уровне корня проекта. Каждый файл — отдельный крейт, который может тестировать публичный API библиотеки.

// tests/integration_test.rs
use my_library::add;

#[test]
fn test_integration() {
    assert_eq!(add(10, 20), 30);
}

Для тестирования бинарных крейтов (с main.rs) можно использовать assert_cmd — он запускает бинарник как внешний процесс и проверяет stdout/stderr.

Property-based testing: proptest и quickcheck

Вместо ручного написания десятков тестовых случаев property-based testing генерирует тысячи случайных входных данных и проверяет инварианты. Два основных фреймворка:

  • proptest — современный, гибкий, с поддержкой стратегий, фильтрации и сжатия примеров (shrink).
  • quickcheck — более старый, вдохновлённый Haskell QuickCheck, проще, но менее мощный.

Пример с proptest:

use proptest::prelude::*;

fn reverse_string(s: &str) -> String {
    s.chars().rev().collect()
}

proptest! {
    #[test]
    fn test_reverse_twice(s in ".*") {
        let reversed = reverse_string(&s);
        let double_reversed = reverse_string(&reversed);
        assert_eq!(s, double_reversed);
    }

    #[test]
    fn test_reverse_length(s in ".*") {
        let reversed = reverse_string(&s);
        assert_eq!(s.len(), reversed.len());
    }
}

Если тест падает, proptest находит минимальный контрпример (shrinking).

Тестирование асинхронного кода: tokio-test

Для асинхронных функций, работающих с tokio, обычные тесты не подходят — нужен асинхронный рантайм. tokio-test предоставляет макрос #[tokio::test].

use tokio::time::{sleep, Duration};

async fn delayed_add(a: u32, b: u32) -> u32 {
    sleep(Duration::from_millis(10)).await;
    a + b
}

#[tokio::test]
async fn test_delayed_add() {
    let result = delayed_add(3, 5).await;
    assert_eq!(result, 8);
}

Также можно использовать futures-test для работы с futures и async-std.

Моки и стабы: mockall и mockito

Для изоляции тестов от внешних зависимостей (БД, API, файловая система) используются моки.

  • mockall — генерация моков на основе трейтов. Поддерживает ожидания (expectations) и проверку последовательности вызовов.
  • mockito — HTTP-мок сервер для тестирования API-клиентов. Запускает локальный сервер на заданном порту.

Рекомендаций