Как считали покрытие Unit-тестами на проекте
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как считали покрытие Unit-тестами на проекте
Покрытие кода тестами (code coverage) — это ключевая метрика качества тестирования. На практике, каждый проект устанавливает минимальный процент покрытия (обычно 70-90%) и контролирует его. Расскажу, как я считал покрытие на своих проектах.
1. Инструменты для расчёта покрытия в Java
JaCoCo (Java Code Coverage) — самый популярный инструмент в Java экосистеме. Он интегрируется с Maven и Gradle и генерирует детальные отчёты.
Другие инструменты:
- Cobertura — старый, но стабильный
- Clover — коммерческий
- OpenClover — open-source версия Clover
- PiTest (Pitest) — для анализа мутационного тестирования
На своих проектах я использовал JaCoCo как основной, и иногда PiTest для более глубокого анализа качества.
2. Интеграция JaCoCo с Maven
Добавляю в pom.xml:
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>PACKAGE</element>
<excludes>
<exclude>*Test</exclude>
</excludes>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Эта конфигурация:
- Собирает информацию о покрытии при запуске тестов
- Генерирует HTML отчёт
- Проверяет, что покрытие не ниже 80%
3. Запуск тестов с JaCoCo
mvn clean test
Отчёт будет в: target/site/jacoco/index.html
Открываю его в браузере и вижу:
- Общий процент покрытия
- Покрытие по пакетам
- Покрытие по классам
- Какие строки не покрыты (помечены красным)
4. Интеграция JaCoCo с Gradle
Добавляю в build.gradle:
plugins {
id 'jacoco'
}
tasks.register('jacocoTestReport', JacocoReport) {
dependsOn test
reports {
xml.required = true
html.required = true
}
}
jacocoTestCoverageVerification {
violationRules {
rule {
element = 'PACKAGE'
excludes = ['*Test']
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.80
}
}
}
}
Запуск:
./gradlew test jacocoTestReport
Отчёт: build/reports/jacoco/test/html/index.html
5. Метрики покрытия
JaCoCo считает несколько типов покрытия:
| Метрика | Описание | Важность |
|---|---|---|
| Line Coverage | Процент строк кода, выполненных тестами | Высокая |
| Branch Coverage | Процент веток (if/else, switch) | Высокая |
| Method Coverage | Процент методов, вызванных тестами | Средняя |
| Class Coverage | Процент классов с хотя бы одним тестом | Низкая |
| Cyclomatic Complexity | Сложность кода | Средняя |
6. Пример расчёта покрытия на реальном классе
public class OrderService {
public boolean processOrder(Order order) {
if (order == null) { // Line 1: Покрыто, Branch 1
throw new IllegalArgumentException("Order is null"); // Line 2: Не покрыто
}
if (order.getAmount() <= 0) { // Line 3: Покрыто, Branch 2
return false; // Line 4: Покрыто
}
order.setStatus("PROCESSING"); // Line 5: Покрыто
return true; // Line 6: Покрыто
}
}
Тесты:
public class OrderServiceTest {
@Test
void testProcessValidOrder() {
OrderService service = new OrderService();
Order order = new Order(100.0);
assertTrue(service.processOrder(order));
// Покрыли: Lines 1, 3, 4 (if (order == null) — false branch), 5, 6
// Branches: branch 1 (false), branch 2 (false)
}
@Test
void testProcessOrderWithZeroAmount() {
OrderService service = new OrderService();
Order order = new Order(0.0);
assertFalse(service.processOrder(order));
// Покрыли: Line 3 (true branch), Line 4
// Branch 2: true branch
}
}
Результат:
- Line Coverage: 5/6 = 83% (не покрыта строка 2)
- Branch Coverage: 3/4 = 75% (не покрыта ветка if (order == null) == true)
Чтобы получить 100%, нужен ещё тест:
@Test
void testProcessNullOrder() {
OrderService service = new OrderService();
assertThrows(IllegalArgumentException.class, () -> service.processOrder(null));
}
Теперь все строки и ветки покрыты.
7. CI/CD интеграция
На GitHub Actions проверяю покрытие в каждом PR:
name: Test Coverage
on: [push, pull_request]
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
java-version: 17
- run: mvn clean test
- uses: codecov/codecov-action@v2
with:
files: ./target/site/jacoco/jacoco.xml
fail_ci_if_error: true
minimum_coverage: 80
Если покрытие упадёт ниже 80%, CI падает и PR не мержится.
8. Инструменты для визуализации
Codecov — облачный сервис для отслеживания покрытия:
# Загрузка отчёта на codecov.io
curl -Os https://uploader.codecov.io/latest/linux/codecov
chmod +x codecov
./codecov -f ./target/site/jacoco/jacoco.xml
Далее на codecov.io вижу:
- Графики покрытия по времени
- Сравнение покрытия между коммитами
- Какие новые строки кода не покрыты
- Статус значка (badge) для README
SonarQube — ещё один популярный инструмент:
mvn clean test sonar:sonar \
-Dsonar.projectKey=my-app \
-Dsonar.host.url=http://localhost:9000 \
-Dsonar.login=mytoken
9. Практический опыт: стандарты покрытия
На разных проектах я использовал разные стандарты:
- Core domain/business logic: 90%+ (критично)
- Services/Use cases: 85%+ (важно)
- Controllers/REST endpoints: 70%+ (средне)
- Utils/Helpers: 80%+ (важно)
- Configuration: 0% (не тестируем)
- Entities/POJO: 50%+ (getters/setters)
10. Когда НЕ нужно 100% покрытие
Исключаю из расчёта:
- Generated code (Lombok, Protocol Buffers)
- Configuration классы
- Exception constructors
- Getters/setters простых POJO
- Code that's impossible to test (например, catch блоки со специфическими condition)
Конфигурация для исключений в JaCoCo:
<exclude>**/config/**</exclude>
<exclude>**/entity/**</exclude>
<exclude>**/*Exception.java</exclude>
11. Мутационное тестирование (PiTest)
Для более глубокого анализа качества тестов использую PiTest:
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.14.0</version>
<configuration>
<targetClasses>
<param>com.myapp.service.*</param>
</targetClasses>
<targetTests>
<param>com.myapp.service.*Test</param>
</targetTests>
</configuration>
</plugin>
Запуск:
mvn org.pitest:pitest-maven:mutationCoverage
ПиTest может обнаружить тесты, которые не проверяют корректность поведения (например, тесты, которые проходят даже если изменить логику).
Вывод
На практике я:
- Использовал JaCoCo как основной инструмент
- Установил минимум 80% покрытия для критических компонентов
- Интегрировал в CI/CD для автоматического контроля
- Использовал Codecov для мониторинга тренда
- Иногда применял PiTest для глубокого анализа
- Исключал некритичный код (config, entities) из расчётов
- Фокусировался на качестве тестов, а не на проценте покрытия
Главное правило: покрытие 100% не гарантирует качество кода, но покрытие 70%+ помогает избежать очевидных ошибок.