🧪 Что это
Этот skill описывает полный набор практик и шаблонов для тестирования кода на Rust — от классического подхода TDD (Red-Green-Refactor) до продвинутых техник: асинхронных тестов, параметризации, property-based testing, моков и измерения покрытия. Он подходит как для новичков, впервые пишущих #[test], так и для команд, внедряющих строгие практики качества в CI.
⚙️ Как работает
📌 TDD-цикл (Red-Green-Refactor)
- Красный (RED) — сначала пишется тест, который гарантированно падает. Реализация кода заменяется на
todo!().
- Зелёный (GREEN) — пишется минимальный код, чтобы тест прошёл.
- Рефакторинг (REFACTOR) — улучшается структура реализации без изменения поведения; тесты остаются зелёными.
Пример простейшего цикла:
// RED
pub fn add(a: i32, b: i32) -> i32 {
todo!()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
}
// GREEN
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
🧩 Виды тестов и инструменты
🔹 Unit‑тесты (модульные)
Организуются в #[cfg(test)] mod tests прямо внутри файлов с кодом. Для утверждений используются макросы assert!, assert_eq!, assert_ne!. Для проверки на панику — атрибут #[should_panic].
🔹 Интеграционные тесты
Размещаются в папке tests/ на уровне корня крейта. Каждый файл = отдельная тестовая binary. Допустимо выделять общие утилиты в tests/common/mod.rs.
🔹 Асинхронные тесты
Для async fn используется крейт tokio и атрибут #[tokio::test]. Позволяет тестировать веб-запросы, таймауты и конкурентные операции.
🔹 Параметризованные тесты с rstest
Позволяет переиспользовать одинаковую логику для разных входных данных. С помощью #[rstest] и #[case] задаются наборы аргументов. #[fixture] — для подготовки общего окружения (например, тестовой БД).
🔹 Property-based тесты с proptest
Вместо ручного перебора кейсов — генерация случайных входных данных и проверка свойств (инвариантов). Примеры:
- кодирование/декодирование всегда возвращает оригинал (roundtrip);
- сортировка сохраняет длину и упорядочивает элементы.
Стратегии (например, valid_email) позволяют генерировать валидные данные заданного формата.
🔹 Моки с mockall
Для изоляции тестируемого модуля от внешних зависимостей (БД, API). На основе трейта автоматически генерируется Mock..., в котором с помощью expect_* задаются ожидаемые вызовы, аргументы (predicate::eq) и возвращаемые значения.
🔹 Doc-тесты
Примеры в документации (///) выполняются как тесты при запуске cargo test --doc. Служат одновременно документацией и проверкой.
🔹 Бенчмарки с criterion
Для замера производительности. На основе крейта criterion можно генерировать HTML-отчёты и отслеживать регрессии.
✅ Измерение покрытия
Установка cargo-llvm-cov (через cargo install или taiki-e/install-action в CI). Команды:
cargo llvm-cov # сводка по строкам
cargo llvm-cov --html # подробный HTML-отчёт
cargo llvm-cov --fail-under-lines 80 # порог качества
Рекомендуемые цели покрытия:
- 100% — критическая бизнес-логика;
- 90%+ — публичный API;
- 80%+ — общий код.
🚀 Полезные команды cargo test
cargo test # все тесты
cargo test --lib # только модульные
cargo test --test api_test # только интеграционные
cargo test --doc # только doc-тесты
cargo test -- --nocapture # показать stdout
cargo test -- --ignored # запустить игнорируемые тесты
cargo test --no-fail-fast # не останавливаться после первой ошибки
📌 Когда использовать
- На старте нового проекта — сразу настроить TDD-цикл и базовую инфраструктуру тестов.
- При добавлении функциональности — писать тест раньше реализации (это улучшает дизайн API и снижает число багов).
- Для критичных к производительности модулей — добавить бенчмарки с
criterion.
- При работе с внешними источниками (БД, HTTP) — задействовать
mockall для изоляции и скорости.
- В continuous integration — обязательный запуск тестов, линтера (
clippy), проверки форматирования (fmt) и порога покрытия.
⚠️ Важно знать
- Тесты — это документация. Пишите их так, чтобы читающий сразу понимал, как использовать ваш код.
- Избегайте
#[should_panic], если можно проверить через `Result::is_err() — это даёт более точную обратную связь.
- Не мокайте всё подряд. Интеграционные тесты с реальной БД или файловой системой часто надёжнее (хотя и медленнее).
- Не используйте
sleep() в тестах. Вместо этого — каналы, барьеры или tokio::time::pause().
- Не игнорируйте нестабильные тесты. Фиксите их или изолируйте. Заигнорированные тесты перестают быть полезными.
В результате skill даёт готовую методологию и набор инструментов, чтобы тестирование в Rust было системным, быстрым и надёжным.
Комментарии
Комментариев пока нет. Будьте первым.