Покрытие кода тестами в Rust: от нуля до уверенности

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

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

Начать курс

Почему покрытие кода — это не просто галочка в CI/CD?

Представьте: вы написали на Rust модуль для парсинга сложных конфигурационных файлов. Компилятор счастлив, тесты проходят, но спустя месяц продакшн падает с ошибкой на редком краевом случае. Знакомо? Именно здесь на сцену выходит покрытие кода тестами в Rust — метрика, которая показывает, какой процент вашего кода действительно выполняется во время тестов.

Но будем честны: погоня за 100% покрытием — это ловушка. Гораздо важнее понимать, какие строки остались без тестов и почему. В этой статье мы разберем три главных инструмента для Rust: tarpaulin, grcov и встроенный LLVM-инструментарий. Вы узнаете, как превратить сухие проценты в actionable инсайты для рефакторинга.

Зачем разработчику Rust следить за покрытием?

Rust славится своей безопасностью памяти, но это не отменяет логических ошибок. Покрытие кода тестами помогает:

  • Обнаружить «мертвый код» — функции или ветки, которые никогда не вызываются.
  • Повысить уверенность при рефакторинге — зная, что критические пути покрыты тестами.
  • Улучшить документацию — тесты часто служат лучшей документацией, если они покрывают все сценарии.

Главная метрика — line coverage (покрытие строк), но мы также посмотрим на branch coverage (покрытие ветвлений), которое в Rust особенно важно из-за обилия match-выражений.

Инструмент №1: cargo-tarpaulin — простота и скорость

tarpaulin — это самый популярный инструмент для покрытия кода в Rust. Он работает «из коробки» и не требует сложной настройки. Установка:

cargo install cargo-tarpaulin

Запуск базового анализа:

cargo tarpaulin --out Html

Эта команда сгенерирует HTML-отчет в папке tarpaulin-report.html. Откроем его — и увидим цветовую карту вашего проекта: зеленые строки (покрытые тестами), красные (непокрытые) и серые (нерабочие, например, комментарии).

Пример: тестируем простую функцию

Создадим файл src/lib.rs с функцией вычисления факториала:

pub fn factorial(n: u32) -> u32 {
    match n {
        0 | 1 => 1,
        _ => n * factorial(n - 1),
    }
}

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

    #[test]
    fn test_factorial() {
        assert_eq!(factorial(0), 1);
        assert_eq!(factorial(1), 1);
        assert_eq!(factorial(5), 120);
    }
}

Запустим tarpaulin:

cargo tarpaulin --ignore-tests --out Html

Флаг --ignore-tests исключает сам код тестов из отчета. Результат: 100% покрытие для этой функции. Но давайте усложним задачу — добавим обработку ошибок:

pub fn factorial_safe(n: i32) -> Result<u32, String> {
    if n < 0 {
        return Err("Отрицательное число!".to_string());
    }
    Ok(factorial(n as u32))
}

Теперь tarpaulin покажет, что ветка Err не покрыта тестами. Добавим тест:

#[test]
fn test_negative() {
    assert!(factorial_safe(-5).is_err());
}

После повторного запуска покрытие снова станет 100%.

Продвинутые настройки tarpaulin

Для больших проектов используйте:

  • --fail-under 80 — аварийный выход, если покрытие ниже 80%.
  • --skip-clean — не пересобирать проект, если изменения незначительны.
  • --engine llvm — использовать LLVM-инструментарий (экспериментально, но точнее).

Инструмент №2: grcov — король интеграций

grcov — это более гибкий инструмент, который работает с LLVM-инструментарием. Он особенно полезен, если вы используете CI/CD (GitHub Actions, GitLab CI) и хотите отправлять отчеты в Coveralls или Codecov.

Установка:

cargo install grcov
rustup component add llvm-tools-preview

Запуск в две команды:

export CARGO_INCREMENTAL=0
export RUSTFLAGS="-Zinstrument-coverage"
cargo build
grcov . --binary-path ./target/debug/ -s . -t html --branch --ignore-not-existing -o ./coverage/

Разберем ключевые флаги:

  • -Zinstrument-coverage — включает сбор профилирующей информации в LLVM.
  • --branch — включает покрытие ветвлений (очень полезно для match и if).
  • --ignore-not-existing — игнорирует файлы, которые не были скомпилированы.

Сравнение tarpaulin и grcov

Оба инструмента хороши, но есть нюансы:

  • Tarpaulin — быстрее, проще, но менее точен для branch coverage.
  • Grcov — точнее (использует LLVM), но требует больше настроек и дольше работает.

Для небольших проектов выбирайте tarpaulin. Для корпоративных или open-source — grcov.

Инструмент №3: LLVM-инструментарий — максимальная точность

Если вам нужен полный контроль, используйте встроенные средства LLVM. Этот метод подходит для глубокого анализа, например, для проверки покрытия ассемблерного кода или inline-функций.

Пример запуска:

export RUSTFLAGS="-Cinstrument-coverage"
cargo test
llvm-profdata merge -sparse default.profraw -o default.profdata
llvm-cov show target/debug/your_project -instr-profile=default.profdata -show-line-counts-or-regions -Xdemangler=rustfilt

Результат — детальный построчный отчет в терминале. Этот метод редко используется напрямую, но лежит в основе grcov.

Метрики: что действительно важно?

Не зацикливайтесь на 100%. Вместо этого следите за:

  • Покрытием критических путей — например, функции аутентификации или обработки платежей.
  • Branch coverage — если в match-выражении 5 веток, а тестами покрыты только 3, это зона риска.
  • Δ-покрытие (delta coverage) — как изменилось покрытие

Блоги

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