Покрытие кода тестами в JavaScript: инструменты и метрики
Представьте, что вы — капитан огромного лайнера. Ваша команда исправно проверяет двигатели, навигацию и системы безопасности. Но однажды вы решаете не проверять систему охлаждения, потому что «она работала вчера». Итог предсказуем: перегрев, поломка, и вы дрейфуете в открытом море. В мире JavaScript такая беспечность называется «отсутствием покрытия кода тестами».
Вы пишете тесты — это здорово. Но как понять, что вы проверили действительно всё, а не просто создали иллюзию безопасности? Ответ — метрики покрытия кода. Они показывают, какая часть вашего приложения была выполнена во время тестов. Без этих метрик вы рискуете пропустить критический баг, который спрятался в непроверенной ветке условия.
В этой статье мы разберем, как правильно измерять покрытие кода тестами в JavaScript, какие инструменты для этого существуют и как интерпретировать метрики, чтобы не навредить себе. Погружаемся в мир code coverage без скуки!
Что такое покрытие кода и почему это важно?
Покрытие кода (code coverage) — это метрика, которая показывает, сколько строк, функций или ветвей вашего кода было выполнено при запуске тестов. Проще говоря, это процент «проверенного» кода.
Многие разработчики считают, что 100% покрытие — это священный грааль. Но это не совсем так. Покрытие — это инструмент, а не цель. Оно помогает:
- Найти «мёртвый» код, который никогда не выполняется (и, возможно, не нужен).
- Понять, какие участки кода остались без тестов, и оценить риск.
- Повысить уверенность в рефакторинге: если покрытие не упало, вы ничего не сломали.
Но есть и подводные камни. 100% покрытие не гарантирует отсутствие багов. Вы можете проверить все строки, но пропустить логическую ошибку в бизнес-правилах. Поэтому покрытие тестами JavaScript нужно рассматривать в комплексе с качеством самих тестов.
Основные метрики покрытия кода
Перед тем как выбирать инструмент, разберемся, какие метрики существуют. Метрики покрытия кода делятся на несколько типов, и каждый из них отвечает на свой вопрос.
Покрытие строк (Line Coverage)
Самая простая метрика. Она показывает, сколько строк исходного кода было выполнено хотя бы один раз. Если у вас 100 строк, и тесты выполнили 80, то покрытие строк — 80%.
Покрытие ветвей (Branch Coverage)
Более глубокая метрика. Она анализирует все возможные пути выполнения кода (ветки if/else, тернарные операторы, switch). Например, если у вас есть условие if (a > 5), есть две ветки: true и false. Если ваш тест проверяет только случай, когда a > 5, покрытие ветвей будет 50%.
Покрытие функций (Function Coverage)
Показывает, сколько функций (или методов) было вызвано во время тестов. Если функция объявлена, но ни разу не вызвана, она не покрыта.
Покрытие операторов (Statement Coverage)
Похоже на покрытие строк, но оперирует не строками, а отдельными операторами (выражениями). В JavaScript эти понятия часто пересекаются, но формально это разные вещи.
Для реальных проектов анализ покрытия JS обычно включает все четыре метрики. Но основное внимание стоит уделять покрытию ветвей, так как именно оно выявляет пропущенные сценарии.
Инструменты для измерения покрытия в JavaScript
К счастью, экосистема JavaScript богата инструментами. Мы рассмотрим самые популярные: от простых до продвинутых.
Istanbul (nyc) — золотой стандарт
Istanbul — это самый популярный инструмент для измерения покрытия кода тестами JavaScript. Он работает практически с любым тест-раннером: Jest, Mocha, Ava, Cypress. После установки вы просто запускаете тесты с оберткой nyc.
Пример установки и запуска:
npm install --save-dev nyc
# Запуск тестов с покрытием
npx nyc mocha test/*.js
После выполнения вы получите отчет в консоли и в виде HTML-страницы, где каждый файл подсвечен зеленым (покрыто) и красным (не покрыто).
Jest — покрытие из коробки
Если вы используете Jest (популярный фреймворк от Facebook), вам не нужен Istanbul отдельно. Jest использует его под капотом. Достаточно добавить флаг --coverage.
Пример для Jest coverage:
// package.json
{
"scripts": {
"test": "jest --coverage"
}
}
После запуска в папке coverage появится отчет. Jest также умеет генерировать покрытие ветвей, если указать настройку collectCoverageFrom.
Cypress — покрытие для E2E-тестов
Для сквозного (end-to-end) тестирования часто используют Cypress. Он также поддерживает Cypress coverage через плагин @cypress/code-coverage. Это особенно полезно, когда вы хотите узнать, какой процент фронтенда покрыт интеграционными тестами.
Пример настройки:
// cypress/support/index.js
import '@cypress/code-coverage/support'
// cypress/plugins/index.js
module.exports = (on, config) => {
require('@cypress/code-coverage/task')(on, config)
return config
}
Как настроить покрытие в реальном проекте?
Теперь перейдем к практике. Допустим, у вас есть простой модуль calc.js с функциями сложения и деления.
// calc.js
function add(a, b) {
return a + b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
module.exports = { add, divide };
Напишем тесты с использованием Jest:
// calc.test.js
const { add, divide } = require('./calc');
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
test('divides 6 by 2 to equal 3', () => {
expect(divide(6, 2)).toBe(3);
});
// Забыли протестировать деление на ноль!
Запустим с покрытием:
npx jest --coverage
В отчете вы увидите, что покрытие строк, возможно, 100%, но покрытие ветвей будет 50%, потому что ветка b === 0 не проверена. Это отличный пример того, как метрики помогают найти пробел в тестах.