Узнайте, что такое покрытие кода, и познакомьтесь с четырьмя распространенными способами его измерения.
Вы слышали фразу «покрытие кода»? В этой статье мы рассмотрим, что такое покрытие кода в тестах и четыре распространенных способа его измерения.
Что такое покрытие кода?
Покрытие кода — это метрика, которая измеряет процент исходного кода, который выполняют ваши тесты. Она помогает вам определить области, в которых может отсутствовать надлежащее тестирование.
Часто запись этих показателей выглядит следующим образом:
Файл | % Заявления | % Ветвь | % Функции | % линий | Непокрытые линии |
---|---|---|---|---|---|
файл.js | 90% | 100% | 90% | 80% | 89,256 |
кофе.js | 55.55% | 80% | 50% | 62,5% | 10-11, 18 |
По мере добавления новых функций и тестов, увеличение процента покрытия кода может дать вам больше уверенности в том, что ваше приложение было тщательно протестировано. Однако есть еще кое-что, что можно узнать.
Четыре распространенных типа покрытия кода
Существует четыре распространенных способа сбора и расчета покрытия кода: покрытие функций, строк, ветвей и операторов.
Чтобы увидеть, как каждый тип покрытия кода вычисляет свой процент, рассмотрим следующий пример кода для расчета ингредиентов кофе:
/* coffee.js */
export function calcCoffeeIngredient(coffeeName, cup = 1) {
let espresso, water;
if (coffeeName === 'espresso') {
espresso = 30 * cup;
return { espresso };
}
if (coffeeName === 'americano') {
espresso = 30 * cup; water = 70 * cup;
return { espresso, water };
}
return {};
}
export function isValidCoffee(name) {
return ['espresso', 'americano', 'mocha'].includes(name);
}
Тесты, проверяющие функцию calcCoffeeIngredient
:
/* coffee.test.js */
import { describe, expect, assert, it } from 'vitest';
import { calcCoffeeIngredient } from '../src/coffee-incomplete';
describe('Coffee', () => {
it('should have espresso', () => {
const result = calcCoffeeIngredient('espresso', 2);
expect(result).to.deep.equal({ espresso: 60 });
});
it('should have nothing', () => {
const result = calcCoffeeIngredient('unknown');
expect(result).to.deep.equal({});
});
});
Вы можете запустить код и тесты в этой интерактивной демонстрации или ознакомиться с репозиторием .
Охват функций
Покрытие кода: 50%
/* coffee.js */
export function calcCoffeeIngredient(coffeeName, cup = 1) {
// ...
}
function isValidCoffee(name) {
// ...
}
Покрытие функций — это простая метрика. Она фиксирует процент функций в вашем коде, которые вызывают ваши тесты.
В примере кода есть две функции: calcCoffeeIngredient
и isValidCoffee
. Тесты вызывают только функцию calcCoffeeIngredient
, поэтому покрытие функции составляет 50%.
Покрытие линии
Покрытие кода: 62,5%
/* coffee.js */
export function calcCoffeeIngredient(coffeeName, cup = 1) {
let espresso, water;
if (coffeeName === 'espresso') {
espresso = 30 * cup;
return { espresso };
}
if (coffeeName === 'americano') {
espresso = 30 * cup; water = 70 * cup;
return { espresso, water };
}
return {};
}
export function isValidCoffee(name) {
return ['espresso', 'americano', 'mocha'].includes(name);
}
Покрытие строк измеряет процент исполняемых строк кода, которые выполнил ваш тестовый набор. Если строка кода осталась невыполненной, это означает, что некоторая часть кода не была протестирована.
В примере кода восемь строк исполняемого кода (выделены красным и зеленым), но тесты не выполняют условие americano
(две строки) и функцию isValidCoffee
(одна строка). Это приводит к покрытию строк на 62,5%.
Обратите внимание, что при покрытии строк не учитываются операторы объявления, такие как function isValidCoffee(name)
и let espresso, water;
, поскольку они не являются исполняемыми.
Покрытие филиала
Покрытие кода: 80%
/* coffee.js */
export function calcCoffeeIngredient(coffeeName, cup = 1) {
// ...
if (coffeeName === 'espresso') {
// ...
return { espresso };
}
if (coffeeName === 'americano') {
// ...
return { espresso, water };
}
return {};
}
…
Покрытие ветвей измеряет процент выполненных ветвей или точек принятия решений в коде, таких как операторы if или циклы. Он определяет, проверяют ли тесты как истинные, так и ложные ветви условных операторов.
В примере кода имеется пять ветвей:
- Вызов
calcCoffeeIngredient
только сcoffeeName
- Вызов
calcCoffeeIngredient
сcoffeeName
иcup
- Кофе - это эспрессо
- Кофе - это американо
- Другой кофе
Тесты охватывают все ветви, кроме условия Coffee is Americano
. Таким образом, покрытие ветвей составляет 80%.
Заявление о покрытии
Покрытие кода: 55,55%
/* coffee.js */
export function calcCoffeeIngredient(coffeeName, cup = 1) {
let espresso, water;
if (coffeeName === 'espresso') {
espresso = 30 * cup;
return { espresso };
}
if (coffeeName === 'americano') {
espresso = 30 * cup; water = 70 * cup;
return { espresso, water };
}
return {};
}
export function isValidCoffee(name) {
return ['espresso', 'americano', 'mocha'].includes(name);
}
Покрытие операторов измеряет процент операторов в вашем коде, которые выполняют ваши тесты. На первый взгляд вы можете спросить: «Разве это не то же самое, что и покрытие строк?» Действительно, покрытие операторов похоже на покрытие строк, но учитывает отдельные строки кода, которые содержат несколько операторов.
В примере кода восемь строк исполняемого кода, но есть девять операторов. Можете ли вы найти строку, содержащую два оператора?
espresso = 30 * cup; water = 70 * cup;
Тесты охватывают только пять из девяти утверждений, поэтому охват утверждений составляет 55,55%.
Если вы всегда пишете по одному утверждению в строке, охват вашей строки будет аналогичен охвату вашего утверждения.
Какой тип покрытия кода следует выбрать?
Большинство инструментов покрытия кода включают эти четыре типа общего покрытия кода. Выбор приоритетной метрики покрытия кода зависит от конкретных требований проекта, методов разработки и целей тестирования.
В целом, покрытие операторов является хорошей отправной точкой, поскольку это простая и понятная метрика. В отличие от покрытия операторов, покрытие ветвей и покрытие функций измеряют, вызывают ли тесты условие (ветвь) или функцию. Поэтому они являются естественным продолжением покрытия операторов.
Достигнув высокого уровня покрытия выписок, можно переходить к покрытию ветвей и покрытию функций.
Является ли покрытие тестами тем же самым, что и покрытие кода?
Нет. Тестовое покрытие и покрытие кода часто путают, но это разные вещи:
- Тестовое покрытие : Качественная метрика, которая измеряет, насколько хорошо тестовый набор охватывает функции программного обеспечения. Она помогает определить уровень риска.
- Покрытие кода : количественная метрика, которая измеряет долю кода, выполненного во время тестирования. Она касается того, какой объем кода покрывают тесты.
Вот упрощенная аналогия: представьте себе веб-приложение как дом.
- Тестовое покрытие показывает, насколько хорошо тесты охватывают комнаты в доме.
- Покрытие кода показывает, какую часть дома прошли тесты.
100% покрытие кода не означает отсутствие ошибок
Хотя достижение высокого покрытия кода при тестировании, безусловно, желательно, 100% покрытие кода не гарантирует отсутствия ошибок или недостатков в вашем коде.
Бессмысленный способ достичь 100% покрытия кода
Рассмотрим следующий тест:
/* coffee.test.js */
// ...
describe('Warning: Do not do this', () => {
it('is meaningless', () => {
calcCoffeeIngredient('espresso', 2);
calcCoffeeIngredient('americano');
calcCoffeeIngredient('unknown');
isValidCoffee('mocha');
expect(true).toBe(true); // not meaningful assertion
});
});
Этот тест достигает 100% покрытия функций, строк, ветвей и операторов, но он не имеет смысла, поскольку фактически не тестирует код. Утверждение expect(true).toBe(true)
всегда будет проходить, независимо от того, работает ли код правильно.
Плохая метрика хуже, чем ее отсутствие
Плохая метрика может дать вам ложное чувство безопасности, что хуже, чем отсутствие метрики вообще. Например, если у вас есть набор тестов, который достигает 100% покрытия кода, но все тесты бессмысленны, то у вас может возникнуть ложное чувство безопасности, что ваш код хорошо протестирован. Если вы случайно удалите или сломаете часть кода приложения, тесты все равно пройдут, даже если приложение больше не будет работать правильно.
Чтобы избежать этого сценария:
- Обзор теста. Пишите и проверяйте тесты, чтобы убедиться, что они осмысленны, и тестируйте код в различных сценариях.
- Используйте покрытие кода как ориентир , а не как единственный показатель эффективности тестирования или качества кода.
Использование покрытия кода в различных типах тестирования
Давайте подробнее рассмотрим, как можно использовать покрытие кода с помощью трех распространенных типов тестов :
- Модульные тесты. Это лучший тип тестов для сбора покрытия кода, поскольку они предназначены для охвата нескольких небольших сценариев и путей тестирования.
- Интеграционные тесты. Они могут помочь собрать покрытие кода для интеграционных тестов, но используйте их с осторожностью. В этом случае вы вычисляете покрытие большей части исходного кода, и может быть сложно определить, какие тесты на самом деле покрывают какие части кода. Тем не менее, вычисление покрытия кода интеграционными тестами может быть полезным для устаревших систем, которые не имеют хорошо изолированных модулей.
- Тесты End-to-end (E2E). Измерение покрытия кода для тестов E2E является сложным и трудным из-за сложной природы этих тестов. Вместо покрытия кода лучшим способом может быть покрытие требований. Это связано с тем, что тесты E2E направлены на покрытие требований вашего теста, а не на фокусировку на исходном коде.
Заключение
Покрытие кода может быть полезной метрикой для измерения эффективности ваших тестов. Оно может помочь вам улучшить качество вашего приложения, гарантируя, что важнейшая логика в вашем коде хорошо протестирована.
Однако помните, что покрытие кода — это всего лишь одна метрика. Обязательно учитывайте и другие факторы, такие как качество ваших тестов и требования к приложению.
Целью не является 100% покрытие кода. Вместо этого следует использовать покрытие кода вместе с хорошо продуманным планом тестирования, включающим в себя различные методы тестирования, включая модульные тесты, интеграционные тесты, сквозные тесты и ручные тесты.
Посмотрите полный пример кода и тесты с хорошим покрытием кода. Вы также можете запустить код и тесты с помощью этой живой демонстрации .
/* coffee.js - a complete example */
export function calcCoffeeIngredient(coffeeName, cup = 1) {
if (!isValidCoffee(coffeeName)) return {};
let espresso, water;
if (coffeeName === 'espresso') {
espresso = 30 * cup;
return { espresso };
}
if (coffeeName === 'americano') {
espresso = 30 * cup; water = 70 * cup;
return { espresso, water };
}
throw new Error (`${coffeeName} not found`);
}
function isValidCoffee(name) {
return ['espresso', 'americano', 'mocha'].includes(name);
}
/* coffee.test.js - a complete test suite */
import { describe, expect, it } from 'vitest';
import { calcCoffeeIngredient } from '../src/coffee-complete';
describe('Coffee', () => {
it('should have espresso', () => {
const result = calcCoffeeIngredient('espresso', 2);
expect(result).to.deep.equal({ espresso: 60 });
});
it('should have americano', () => {
const result = calcCoffeeIngredient('americano');
expect(result.espresso).to.equal(30);
expect(result.water).to.equal(70);
});
it('should throw error', () => {
const func = () => calcCoffeeIngredient('mocha');
expect(func).toThrowError(new Error('mocha not found'));
});
it('should have nothing', () => {
const result = calcCoffeeIngredient('unknown')
expect(result).to.deep.equal({});
});
});
Узнайте, что такое покрытие кода, и познакомьтесь с четырьмя распространенными способами его измерения.
Вы слышали фразу «покрытие кода»? В этой статье мы рассмотрим, что такое покрытие кода в тестах и четыре распространенных способа его измерения.
Что такое покрытие кода?
Покрытие кода — это метрика, которая измеряет процент исходного кода, который выполняют ваши тесты. Она помогает вам определить области, в которых может отсутствовать надлежащее тестирование.
Часто запись этих показателей выглядит следующим образом:
Файл | % Заявления | % Ветвь | % Функции | % линий | Непокрытые линии |
---|---|---|---|---|---|
файл.js | 90% | 100% | 90% | 80% | 89,256 |
кофе.js | 55.55% | 80% | 50% | 62,5% | 10-11, 18 |
По мере добавления новых функций и тестов, увеличение процента покрытия кода может дать вам больше уверенности в том, что ваше приложение было тщательно протестировано. Однако есть еще кое-что, что можно узнать.
Четыре распространенных типа покрытия кода
Существует четыре распространенных способа сбора и расчета покрытия кода: покрытие функций, строк, ветвей и операторов.
Чтобы увидеть, как каждый тип покрытия кода вычисляет свой процент, рассмотрим следующий пример кода для расчета ингредиентов кофе:
/* coffee.js */
export function calcCoffeeIngredient(coffeeName, cup = 1) {
let espresso, water;
if (coffeeName === 'espresso') {
espresso = 30 * cup;
return { espresso };
}
if (coffeeName === 'americano') {
espresso = 30 * cup; water = 70 * cup;
return { espresso, water };
}
return {};
}
export function isValidCoffee(name) {
return ['espresso', 'americano', 'mocha'].includes(name);
}
Тесты, проверяющие функцию calcCoffeeIngredient
:
/* coffee.test.js */
import { describe, expect, assert, it } from 'vitest';
import { calcCoffeeIngredient } from '../src/coffee-incomplete';
describe('Coffee', () => {
it('should have espresso', () => {
const result = calcCoffeeIngredient('espresso', 2);
expect(result).to.deep.equal({ espresso: 60 });
});
it('should have nothing', () => {
const result = calcCoffeeIngredient('unknown');
expect(result).to.deep.equal({});
});
});
Вы можете запустить код и тесты в этой интерактивной демонстрации или ознакомиться с репозиторием .
Охват функций
Покрытие кода: 50%
/* coffee.js */
export function calcCoffeeIngredient(coffeeName, cup = 1) {
// ...
}
function isValidCoffee(name) {
// ...
}
Покрытие функций — это простая метрика. Она фиксирует процент функций в вашем коде, которые вызывают ваши тесты.
В примере кода есть две функции: calcCoffeeIngredient
и isValidCoffee
. Тесты вызывают только функцию calcCoffeeIngredient
, поэтому покрытие функции составляет 50%.
Покрытие линии
Покрытие кода: 62,5%
/* coffee.js */
export function calcCoffeeIngredient(coffeeName, cup = 1) {
let espresso, water;
if (coffeeName === 'espresso') {
espresso = 30 * cup;
return { espresso };
}
if (coffeeName === 'americano') {
espresso = 30 * cup; water = 70 * cup;
return { espresso, water };
}
return {};
}
export function isValidCoffee(name) {
return ['espresso', 'americano', 'mocha'].includes(name);
}
Покрытие строк измеряет процент исполняемых строк кода, которые выполнил ваш тестовый набор. Если строка кода осталась невыполненной, это означает, что некоторая часть кода не была протестирована.
В примере кода восемь строк исполняемого кода (выделены красным и зеленым), но тесты не выполняют условие americano
(две строки) и функцию isValidCoffee
(одна строка). Это приводит к покрытию строк на 62,5%.
Обратите внимание, что при покрытии строк не учитываются операторы объявления, такие как function isValidCoffee(name)
и let espresso, water;
, поскольку они не являются исполняемыми.
Покрытие филиала
Покрытие кода: 80%
/* coffee.js */
export function calcCoffeeIngredient(coffeeName, cup = 1) {
// ...
if (coffeeName === 'espresso') {
// ...
return { espresso };
}
if (coffeeName === 'americano') {
// ...
return { espresso, water };
}
return {};
}
…
Покрытие ветвей измеряет процент выполненных ветвей или точек принятия решений в коде, таких как операторы if или циклы. Он определяет, проверяют ли тесты как истинные, так и ложные ветви условных операторов.
В примере кода имеется пять ветвей:
- Вызов
calcCoffeeIngredient
только сcoffeeName
- Вызов
calcCoffeeIngredient
сcoffeeName
иcup
- Кофе - это эспрессо
- Кофе - это американо
- Другой кофе
Тесты охватывают все ветви, кроме условия Coffee is Americano
. Таким образом, покрытие ветвей составляет 80%.
Заявление о покрытии
Покрытие кода: 55,55%
/* coffee.js */
export function calcCoffeeIngredient(coffeeName, cup = 1) {
let espresso, water;
if (coffeeName === 'espresso') {
espresso = 30 * cup;
return { espresso };
}
if (coffeeName === 'americano') {
espresso = 30 * cup; water = 70 * cup;
return { espresso, water };
}
return {};
}
export function isValidCoffee(name) {
return ['espresso', 'americano', 'mocha'].includes(name);
}
Покрытие операторов измеряет процент операторов в вашем коде, которые выполняют ваши тесты. На первый взгляд вы можете спросить: «Разве это не то же самое, что и покрытие строк?» Действительно, покрытие операторов похоже на покрытие строк, но учитывает отдельные строки кода, которые содержат несколько операторов.
В примере кода восемь строк исполняемого кода, но есть девять операторов. Можете ли вы найти строку, содержащую два оператора?
espresso = 30 * cup; water = 70 * cup;
Тесты охватывают только пять из девяти утверждений, поэтому охват утверждений составляет 55,55%.
Если вы всегда пишете по одному утверждению в строке, охват вашей строки будет аналогичен охвату вашего утверждения.
Какой тип покрытия кода следует выбрать?
Большинство инструментов покрытия кода включают эти четыре типа общего покрытия кода. Выбор приоритетной метрики покрытия кода зависит от конкретных требований проекта, методов разработки и целей тестирования.
В целом, покрытие операторов является хорошей отправной точкой, поскольку это простая и понятная метрика. В отличие от покрытия операторов, покрытие ветвей и покрытие функций измеряют, вызывают ли тесты условие (ветвь) или функцию. Поэтому они являются естественным продолжением покрытия операторов.
Достигнув высокого уровня покрытия выписок, можно переходить к покрытию ветвей и покрытию функций.
Является ли покрытие тестами тем же самым, что и покрытие кода?
Нет. Тестовое покрытие и покрытие кода часто путают, но это разные вещи:
- Тестовое покрытие : Качественная метрика, которая измеряет, насколько хорошо тестовый набор охватывает функции программного обеспечения. Она помогает определить уровень риска.
- Покрытие кода : количественная метрика, которая измеряет долю кода, выполненного во время тестирования. Она касается того, какой объем кода покрывают тесты.
Вот упрощенная аналогия: представьте себе веб-приложение как дом.
- Тестовое покрытие показывает, насколько хорошо тесты охватывают комнаты в доме.
- Покрытие кода показывает, какую часть дома прошли тесты.
100% покрытие кода не означает отсутствие ошибок
Хотя достижение высокого покрытия кода при тестировании, безусловно, желательно, 100% покрытие кода не гарантирует отсутствия ошибок или недостатков в вашем коде.
Бессмысленный способ достичь 100% покрытия кода
Рассмотрим следующий тест:
/* coffee.test.js */
// ...
describe('Warning: Do not do this', () => {
it('is meaningless', () => {
calcCoffeeIngredient('espresso', 2);
calcCoffeeIngredient('americano');
calcCoffeeIngredient('unknown');
isValidCoffee('mocha');
expect(true).toBe(true); // not meaningful assertion
});
});
Этот тест достигает 100% покрытия функций, строк, ветвей и операторов, но он не имеет смысла, поскольку фактически не тестирует код. Утверждение expect(true).toBe(true)
всегда будет проходить, независимо от того, работает ли код правильно.
Плохая метрика хуже, чем ее отсутствие
Плохая метрика может дать вам ложное чувство безопасности, что хуже, чем отсутствие метрики вообще. Например, если у вас есть набор тестов, который достигает 100% покрытия кода, но все тесты бессмысленны, то у вас может возникнуть ложное чувство безопасности, что ваш код хорошо протестирован. Если вы случайно удалите или сломаете часть кода приложения, тесты все равно пройдут, даже если приложение больше не будет работать правильно.
Чтобы избежать этого сценария:
- Обзор теста. Пишите и проверяйте тесты, чтобы убедиться, что они осмысленны, и тестируйте код в различных сценариях.
- Используйте покрытие кода как ориентир , а не как единственный показатель эффективности тестирования или качества кода.
Использование покрытия кода в различных типах тестирования
Давайте подробнее рассмотрим, как можно использовать покрытие кода с помощью трех распространенных типов тестов :
- Модульные тесты. Это лучший тип тестов для сбора покрытия кода, поскольку они предназначены для охвата нескольких небольших сценариев и путей тестирования.
- Интеграционные тесты. Они могут помочь собрать покрытие кода для интеграционных тестов, но используйте их с осторожностью. В этом случае вы вычисляете покрытие большей части исходного кода, и может быть сложно определить, какие тесты на самом деле покрывают какие части кода. Тем не менее, вычисление покрытия кода интеграционными тестами может быть полезным для устаревших систем, которые не имеют хорошо изолированных модулей.
- Тесты End-to-end (E2E). Измерение покрытия кода для тестов E2E является сложным и трудным из-за сложной природы этих тестов. Вместо покрытия кода лучшим способом может быть покрытие требований. Это связано с тем, что тесты E2E направлены на покрытие требований вашего теста, а не на фокусировку на исходном коде.
Заключение
Покрытие кода может быть полезной метрикой для измерения эффективности ваших тестов. Оно может помочь вам улучшить качество вашего приложения, гарантируя, что важнейшая логика в вашем коде хорошо протестирована.
Однако помните, что покрытие кода — это всего лишь одна метрика. Обязательно учитывайте и другие факторы, такие как качество ваших тестов и требования к приложению.
Целью не является 100% покрытие кода. Вместо этого следует использовать покрытие кода вместе с хорошо продуманным планом тестирования, включающим в себя различные методы тестирования, включая модульные тесты, интеграционные тесты, сквозные тесты и ручные тесты.
Посмотрите полный пример кода и тесты с хорошим покрытием кода. Вы также можете запустить код и тесты с помощью этой живой демонстрации .
/* coffee.js - a complete example */
export function calcCoffeeIngredient(coffeeName, cup = 1) {
if (!isValidCoffee(coffeeName)) return {};
let espresso, water;
if (coffeeName === 'espresso') {
espresso = 30 * cup;
return { espresso };
}
if (coffeeName === 'americano') {
espresso = 30 * cup; water = 70 * cup;
return { espresso, water };
}
throw new Error (`${coffeeName} not found`);
}
function isValidCoffee(name) {
return ['espresso', 'americano', 'mocha'].includes(name);
}
/* coffee.test.js - a complete test suite */
import { describe, expect, it } from 'vitest';
import { calcCoffeeIngredient } from '../src/coffee-complete';
describe('Coffee', () => {
it('should have espresso', () => {
const result = calcCoffeeIngredient('espresso', 2);
expect(result).to.deep.equal({ espresso: 60 });
});
it('should have americano', () => {
const result = calcCoffeeIngredient('americano');
expect(result.espresso).to.equal(30);
expect(result.water).to.equal(70);
});
it('should throw error', () => {
const func = () => calcCoffeeIngredient('mocha');
expect(func).toThrowError(new Error('mocha not found'));
});
it('should have nothing', () => {
const result = calcCoffeeIngredient('unknown')
expect(result).to.deep.equal({});
});
});