Качество ПО и покрытие тестами
Покрытие тестами (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 |
----------|---------|----------|---------|---------|-------------------
Целевые значения покрытия: какие значения имеют смысл?
| Тип проекта | Рекомендуемое покрытие | Обоснование |
|---|---|---|
| Библиотека / SDK | 90-100% | Высокая стабильность, много потребителей |
| Веб-приложение | 70-80% | Прагматичный подход, фокус на бизнес-логике |
| Прототип / MVP | 40-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)— находит тесты, которые не проверяют ровно 90return price * 0.8→return price * 0.9— находит тесты без точных утвержденийreturn 'A'→return 'B'— находит тесты, которые не проверяют возвращаемое значение
Обзор инструментов
| Инструмент | Язык | Метрики | Особенность |
|---|---|---|---|
| Istanbul/nyc | JavaScript | Line, Branch, Func | Стандарт в Jest |
| Coverage.py | Python | Line, Branch | Стандарт в pytest |
| JaCoCo | Java | Line, Branch, Method | Стандарт в Maven/Gradle |
| gcov | C/C++ | Line, Branch | Встроен в GCC |
| Stryker | JS/TS | Mutation Score | Mutation Testing |
| PIT | Java | Mutation Score | Mutation 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?
2. Достаточно ли 100% Coverage?
3. Разумный порог?
4. Что такое Mutation Testing?
5. Что такое Path Coverage?
6. Как настроить Coverage в Jest?
7. Что такое Istanbul?
8. Можно ли принудить Coverage в CI/CD?
9. Function Coverage?
10. Что такое Coverage-Gaming?
11. Инструменты для других языков?
12. Что такое istanbul ignore?
13. Что такое LCOV-report?
14. Coverage в legacy-коде?
15. Coverage vs качество тестов?
Дальше по пути Softwarequalität
Следующая статья в пути Softwarequalität посвящена Clean Code и SOLID принципам — основам для читаемого, поддерживаемого и расширяемого кода.
Источники
- https://istanbul.js.org/
- https://stryker-mutator.io/
- https://jestjs.io/docs/configuration#coveragethreshold-object
- https://iso25000.com/index.php/en/iso-25000-standards/iso-25010.html
Рекомендации по книгам о качестве ПО
Если ты хочешь углубиться в тестовое покрытие, качество ПО и тестирование, рекомендуем следующие книги:
Keine Bücher für Kategorie "software-engineering" gefunden.



