Покрытие кода тестами в TypeScript: лучшие инструменты и метрики

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

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

Начать курс

Покрытие кода тестами в TypeScript: как перестать гадать и начать измерять

Представьте, что вы собираете сложный механизм с множеством шестерёнок. Вы уверены, что собрали всё правильно, но стоит нажать кнопку «Пуск», как раздаётся скрежет и механизм заклинивает. Знакомая ситуация? В мире TypeScript-разработки такие «механизмы» — это наши приложения. А покрытие кода тестами — это контрольная сборка, которая показывает, какие детали мы проверили, а какие оставили на волю случая.

Многие разработчики относятся к метрикам покрытия скептически: «Погоня за процентами — зло», «100% покрытие не гарантирует отсутствие багов». И это правда. Но, как и любой инструмент, покрытие кода может быть полезным или вредным — всё зависит от того, как им пользоваться. В этой статье мы разберём, как правильно измерять покрытие в TypeScript, какие инструменты для этого существуют и как не превратить метрики в самоцель.

Что такое покрытие кода и почему TypeScript — не исключение

Покрытие кода (code coverage) — это метрика, показывающая, какая часть исходного кода была выполнена во время тестов. В TypeScript, который компилируется в JavaScript, измерение покрытия происходит на уровне скомпилированного кода, но современные инструменты умеют красиво отображать результаты в терминах исходного TS-кода.

Основные метрики покрытия, которые нужно знать:

  • Line coverage — процент строк кода, которые были выполнены хотя бы один раз.
  • Branch coverage — процент ветвлений (if/else, switch, тернарные операторы), которые были протестированы.
  • Function coverage — процент функций/методов, которые были вызваны.
  • Statement coverage — процент отдельных инструкций (операторов) в коде.

Для TypeScript-проектов особенно важна branch coverage, так как строгая типизация не спасает от логических ошибок в условиях. Вы можете правильно типизировать все переменные, но забыть про случай, когда массив пуст, а функция ожидает хотя бы один элемент.

Инструменты для измерения покрытия в TypeScript

1. Istanbul (nyc) — дедушка всех покрытий

Istanbul — это самый популярный инструмент для измерения покрытия JavaScript и TypeScript кода. Он работает как самостоятельная утилита (через пакет nyc) или как встроенный модуль в тестовых фреймворках.

Чтобы начать работу с Istanbul в TypeScript-проекте, установите:

npm install --save-dev nyc typescript ts-node

Создайте файл .nycrc для конфигурации:

{
  "extends": "@istanbuljs/nyc-config-typescript",
  "all": true,
  "include": ["src/**/*.ts"],
  "exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"],
  "reporter": ["text", "html", "lcov"],
  "check-coverage": true,
  "branches": 80,
  "lines": 85,
  "functions": 85,
  "statements": 85
}

Запуск тестов с покрытием:

npx nyc mocha --require ts-node/register src/**/*.test.ts

Istanbul генерирует отчёты в консоли (text) и в виде HTML-страниц, где зелёным подсвечен покрытый код, а красным — непокрытый. Это очень наглядно.

2. Jest — всё в одном

Jest — самый популярный тестовый фреймворк для TypeScript. Он уже включает встроенный инструмент покрытия на основе Istanbul. Вам не нужно устанавливать ничего дополнительно.

Базовая настройка в jest.config.ts:

import type { Config } from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  collectCoverage: true,
  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'html', 'lcov'],
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.test.ts',
    '!src/**/*.spec.ts',
    '!src/types/**/*.ts'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 85,
      lines: 85,
      statements: 85
    }
  }
};

export default config;

Команда для запуска:

npx jest --coverage

Jest автоматически создаст папку coverage с HTML-отчётом. Откройте coverage/lcov-report/index.html в браузере — и вы увидите детальную карту покрытия вашего проекта.

3. Vitest — современный и быстрый

Vitest — это молодой, но мощный тестовый фреймворк, созданный для Vite-экосистемы. Он полностью совместим с Jest API, но работает в 2-3 раза быстрее за счёт нативной поддержки ESM и умного кеширования.

Настройка покрытия в vitest.config.ts:

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    coverage: {
      provider: 'istanbul', // или 'c8' (ещё быстрее)
      reporter: ['text', 'html', 'lcov'],
      include: ['src/**/*.ts'],
      exclude: ['src/**/*.test.ts', 'src/**/*.spec.ts'],
      thresholds: {
        branches: 80,
        functions: 85,
        lines: 85,
        statements: 85
      }
    }
  }
});

Vitest поддерживает два провайдера покрытия: istanbul (классика) и c8 (на основе V8, работает быстрее). Для TypeScript-проектов рекомендую использовать c8 — он точнее и быстрее.

Запуск:

npx vitest --coverage

Пример: пишем тесты и смотрим покрытие

Давайте на практике посмотрим, как работает покрытие. Создадим простой модуль для расчёта скидки:

// src/discount.ts

export function calculateDiscount(price: number, isPremium: boolean): number {
  if (price <= 0) {
    throw new Error('Price must be positive');
  }

  let discount = 0;

  if (isPremium) {
    discount = 0.2;
  }

  if (price > 1000) {
    discount = Math.max(discount, 0.15);
  }

  return price * (1 - discount);
}

Напишем тесты (неполные, чтобы увидеть разницу в покрытии):

// src/discount.test.ts

import { calculateDiscount } from './discount';

describe('calculateDiscount', () => {
  it('should apply premium discount', () => {
    const result = calculateDiscount(500, true);
    expect(result).toBe(400); // 500 * 0.8
  });

  it('should throw error for negative price', () => {
    expect(() => calculateDiscount(-100, false)).toThrow('Price must be positive');
  });
});

Запустим тесты с покрытием через Jest и посмотрим результат:

----------------------|---------|----------|---------|---------|-------------------
File                  | % St                

Блоги

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