Skip to content
IRC-CodingIRC-Coding
testabdeckungCode CoverageLine CoverageBranch CoverageMutation TestingJestIstanbulкачество программного обеспечения

Code Coverage и тестирование: метрики и стратегии

Testabdeckung, Line Coverage, Branch Coverage, Mutation Testing, цели покрытия и примеры с Jest и Istanbul.

S

schutzgeist

6 min read
Code Coverage и тестирование: метрики и стратегии

Качество ПО и покрытие тестами

Покрытие тестами (Code Coverage) — одна из ключевых метрик качества программного обеспечения. Она показывает, какой процент кода выполняется во время автоматизированного тестирования. Однако высокое покрытие само по себе не гарантирует высокое качество: важно понимать, что и как измеряется.

Суть в двух словах

Покрытие тестами количественно выражает долю кода, которая выполняется при запуске тестов. Основные метрики: Line Coverage, Branch Coverage и Function Coverage. Значение покрытия 80% означает, что 80% строк кода были достигнуты минимум один раз во время выполнения тестов. Это, однако, ничего не говорит о том, содержат ли сами тесты правильные проверки.

Техническое определение

Code Coverage измеряет степень выполнения исходного кода программы во время тестирования. Выражается в процентах и может измеряться на разных уровнях: строки, операторы, ветвления (branches), функции и пути выполнения. Инструменты для анализа покрытия инструментируют код во время его работы и записывают, какие участки кода были пройдены. Метрика помогает выявить неprotestированные области, но не гарантирует отсутствие ошибок: тест может выполнить строку, не проверив при этом ожидаемое поведение.

Кому нужно покрытие тестами и почему

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

  • Разработчиков: чтобы увидеть, какие участки кода остались без тестов
  • Команд: для определения минимального порога качества
  • QA-инженеров: для выявления рискованных участков кода
  • Руководителей проектов: для оценки зрелости стратегии тестирования
  • Compliance: в регулируемых отраслях (медицина, автомобилестроение, финансы) пороги покрытия часто обязательны

Метрики покрытия подробнее

Line Coverage (Покрытие по строкам)

Показывает, какой процент строк исходного кода был выполнен тестами. Это самая простая и широко используемая метрика.

function classify(score) {
  if (score >= 90) return 'A';    // Строка 1
  if (score >= 80) return 'B';    // Строка 2
  if (score >= 70) return 'C';    // Строка 3
  return 'F';                      // Строка 4
}

Один тест с classify(95) покрывает только строку 1 → 25% Line Coverage. Два теста с classify(95) и classify(65) покрывают строки 1 и 4 → 50%.

Branch Coverage (Покрытие по ветвлениям)

Показывает, какой процент ветвлений (if/else, switch, тернарный оператор) был выполнен при тестировании. Каждое ветвление имеет два пути: true и false.

function classify(score) {
  if (score >= 90) return 'A';    // Ветвление 1: true/false
  if (score >= 80) return 'B';    // Ветвление 2: true/false
  if (score >= 70) return 'C';    // Ветвление 3: true/false
  return 'F';
}

Всего 6 ветвлений (3× true, 3× false). Один тест с classify(95) покрывает только Branch 1-true, но не Branch 1-false → 1/6 = 17% Branch Coverage.

Function Coverage (Покрытие по функциям)

Показывает, какой процент функций был вызван минимум один раз. Особенно важна для модулей с большим количеством небольших функций.

Path Coverage (Покрытие по путям)

Показывает, какой процент возможных путей выполнения кода был протестирован. Это самая строгая метрика, так как она учитывает все комбинации ветвлений. При n независимых ветвлениях существует 2^n путей, поэтому Path Coverage часто нереалистичен для сложного кода.

Statement Coverage (Покрытие по операторам)

Похожа на Line Coverage, но измеряется на уровне отдельных операторов вместо строк. В одной строке может находиться несколько операторов.

Практический пример: Jest с Istanbul Coverage

Настройка

// package.json
{
  "scripts": {
    "test": "jest",
    "test:coverage": "jest --coverage"
  },
  "jest": {
    "collectCoverageFrom": [
      "src/**/*.js",
      "!src/**/*.spec.js",
      "!src/index.js"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80,
        "statements": 80
      }
    }
  }
}

Модуль примера

// src/utils/discount.js
export function calculateDiscount(price, customerType) {
  if (typeof price !== 'number' || price < 0) {
    throw new Error('Price must be a non-negative number');
  }

  switch (customerType) {
    case 'premium':
      return price * 0.8;   // скидка 20%
    case 'vip':
      return price * 0.7;   // скидка 30%
    case 'standard':
      return price;          // без скидки
    default:
      throw new Error(`Unknown customer type: ${customerType}`);
  }
}

Тесты с полным покрытием по ветвлениям

// src/utils/discount.spec.js
import { calculateDiscount } from './discount.js';

describe('calculateDiscount', () => {
  test('premium customer gets 20% discount', () => {
    expect(calculateDiscount(100, 'premium')).toBe(80);
  });

  test('vip customer gets 30% discount', () => {
    expect(calculateDiscount(100, 'vip')).toBe(70);
  });

  test('standard customer gets no discount', () => {
    expect(calculateDiscount(100, 'standard')).toBe(100);
  });

  test('throws on negative price', () => {
    expect(() => calculateDiscount(-1, 'premium'))
      .toThrow('Price must be a non-negative number');
  });

  test('throws on non-number price', () => {
    expect(() => calculateDiscount('100', 'premium'))
      .toThrow('Price must be a non-negative number');
  });

  test('throws on unknown customer type', () => {
    expect(() => calculateDiscount(100, 'unknown'))
      .toThrow('Unknown customer type: unknown');
  });
});

Отчет о покрытии

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |
 discount |     100 |      100 |     100 |     100 |
----------|---------|----------|---------|---------|-------------------

Целевые значения покрытия: какие значения имеют смысл?

Тип проектаРекомендуемое покрытиеОбоснование
Библиотека / SDK90-100%Высокая стабильность, много потребителей
Веб-приложение70-80%Прагматичный подход, фокус на бизнес-логике
Прототип / MVP40-60%Быстрая итерация, тесты для ядра
Регулируемое ПО100% BranchОбязательно (IEC 62304, DO-178C)
Унаследованный код50%+ постепенноУвеличение при рефакторинге

Правило большого пальца: 80% покрытия по строкам с хорошими проверками ценнее, чем 100% покрытия без проверок.

Частые ошибки

1. 100% покрытия без проверок

// Плохо: строка выполняется, но ничего не проверяется
test('calculateDiscount runs', () => {
  calculateDiscount(100, 'premium'); // Нет проверки!
});

Покрытие: 100%, качество: 0%.

2. Игры с покрытием

Разработчики пишут тесты только для достижения порогов покрытия, без смысловых проверок. Решение: используйте mutation testing.

3. Чрезмерное использование игнорирования

/* istanbul ignore next */
export function complexFunction() { ... }

Каждое ignore скрывает непротестированный код. Используйте только для специфичного кода платформы или намеренно исключаемых путей обработки ошибок.

4. Измерение только Line Coverage

Line Coverage — самая слабая метрика. Branch Coverage находит неиспытанные граничные случаи, которые Line Coverage пропускает.

Mutation Testing: следующий уровень

Mutation Testing систематически изменяет код (например, > на >=, + на -) и проверяет, будут ли тесты перехватывать мутантов. Если мутант выживает, тест недостаточен.

# Stryker Mutation Testing для JavaScript
npx stryker run

Примеры мутантов:

  • if (score >= 90)if (score > 90) — находит тесты, которые не проверяют ровно 90
  • return price * 0.8return price * 0.9 — находит тесты без точных утверждений
  • return 'A'return 'B' — находит тесты, которые не проверяют возвращаемое значение

Обзор инструментов

ИнструментЯзыкМетрикиОсобенность
Istanbul/nycJavaScriptLine, Branch, FuncСтандарт в Jest
Coverage.pyPythonLine, BranchСтандарт в pytest
JaCoCoJavaLine, Branch, MethodСтандарт в Maven/Gradle
gcovC/C++Line, BranchВстроен в GCC
StrykerJS/TSMutation ScoreMutation Testing
PITJavaMutation ScoreMutation Testing

Ключевые пункты для проверки

  • Различие между Line, Branch, Path и Function Coverage
  • Coverage — необходимое, но не достаточное условие качества
  • Пороги Coverage в CI/CD как Quality Gate
  • Mutation Testing в дополнение к измерению Coverage
  • Coverage-инструменты: Istanbul, JaCoCo, Coverage.py, gcov
  • 100% Coverage без утверждений бесполезен
  • ISO 25010: тестируемость как характеристика качества

FAQ

1. Line vs Branch Coverage?

Line измеряет строки, Branch измеряет ветвления (true/false). Branch строже.

2. Достаточно ли 100% Coverage?

Нет, без утверждений 100% Coverage бесполезен.

3. Разумный порог?

Веб-приложения 70-80%, библиотеки 90-100%.

4. Что такое Mutation Testing?

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

5. Что такое Path Coverage?

Измеряет все пути выполнения. Самая строгая метрика, часто нереалистична.

6. Как настроить Coverage в Jest?

Установить coverageThreshold в jest.config.js.

7. Что такое Istanbul?

Стандартный инструмент Coverage для JavaScript, используется Jest.

8. Можно ли принудить Coverage в CI/CD?

Да, как Quality Gate против неиспытанного кода.

9. Function Coverage?

Измеряет, сколько функций было вызвано.

10. Что такое Coverage-Gaming?

Тесты без утверждений только ради порога. Решение: Mutation Testing.

11. Инструменты для других языков?

Coverage.py (Python), JaCoCo (Java), gcov (C/C++).

12. Что такое istanbul ignore?

Исключает код из Coverage, использовать осторожно.

13. Что такое LCOV-report?

Стандартный формат данных Coverage для интеграции в CI/CD.

14. Coverage в legacy-коде?

Писать характеризующие тесты, постепенно рефакторить.

15. Coverage vs качество тестов?

Coverage измеряет выполнение, качество тестов измеряет обнаружение ошибок.

Дальше по пути Softwarequalität

Следующая статья в пути Softwarequalität посвящена Clean Code и SOLID принципам — основам для читаемого, поддерживаемого и расширяемого кода.

Источники

  1. https://istanbul.js.org/
  2. https://stryker-mutator.io/
  3. https://jestjs.io/docs/configuration#coveragethreshold-object
  4. https://iso25000.com/index.php/en/iso-25000-standards/iso-25010.html

Рекомендации по книгам о качестве ПО

Если ты хочешь углубиться в тестовое покрытие, качество ПО и тестирование, рекомендуем следующие книги:

Keine Bücher für Kategorie "software-engineering" gefunden.

Назад к блогу
Share:

Nächster Artikel in Качество программного обеспечения

Weiterlesen
Static Code Analysis Tools: ESLint, SonarQube, Pylint

Похожие статьи