Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Mutation Testing в Java
Mutation Testing — это техника для оценки качества набора тестов путём внесения небольших изменений (мутаций) в исходный код и проверки, поймут ли тесты эти изменения. Это позволяет обнаружить недостаточно покрытые участки кода.
Проблема: Code Coverage недостаточен
Просто иметь 80% code coverage недостаточно:
public int calculateDiscount(int age, int amount) {
if (age >= 65) {
return amount / 2; // Скидка для пенсионеров
}
return amount;
}
// Тест с плохими assertions
@Test
void testDiscount() {
int result = calculateDiscount(70, 100);
// ❌ Тест не проверяет результат!
}
Тест покрывает 100% кода, но не проверяет корректность!
Как работает Mutation Testing
Шаг 1: Исходный код
public int add(int a, int b) {
return a + b;
}
Шаг 2: Внести мутацию (изменить код)
// Мутация 1: изменить + на -
public int add(int a, int b) {
return a - b; // ❌ Мутация
}
// Мутация 2: изменить возвращаемое значение
public int add(int a, int b) {
return a; // ❌ Мутация
}
// Мутация 3: изменить константу
if (age >= 65) { // исходный код
if (age > 65) { // ❌ Мутация
Шаг 3: Запустить тесты на мутированном коде
@Test
void testAdd() {
int result = add(2, 3);
assertEquals(5, result); // Тест выявит мутацию!
}
// С мутацией a - b результат 2 - 3 = -1
// assertEquals(5, -1) FAIL ✓ Мутация убита
Шаг 4: Оценить качество тестов
- Если тесты выявили мутацию → мутация "убита" (killed)
- Если тесты не выявили → мутация "выжила" (survived)
Типы мутаций
Арифметические мутации:
// Исходный код
int sum = a + b;
// Мутации
int sum = a - b; // + → -
int sum = a * b; // + → *
int sum = a / b; // + → /
int sum = a % b; // + → %
Логические мутации:
// Исходный код
if (age >= 65) { }
// Мутации
if (age > 65) { } // >= → >
if (age <= 65) { } // >= → <=
if (age == 65) { } // >= → ==
Мутации возвращаемых значений:
// Исходный код
return true;
// Мутация
return false;
Мутации переменных:
// Исходный код
result = user.getAge();
// Мутация
result = 0; // или другое значение
Инструменты для Mutation Testing
PIT (Pitest) — самый популярный в Java экосистеме:
<!-- pom.xml -->
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.14.2</version>
<configuration>
<targetClasses>
<param>com.example.*</param>
</targetClasses>
<targetTests>
<param>com.example.*Test</param>
</targetTests>
</configuration>
</plugin>
Запуск:
mvn test-compile org.pitest:pitest-maven:mutationCoverage
Практический пример с PIT
Исходный код:
public class Calculator {
public int multiply(int a, int b) {
if (a == 0 || b == 0) {
return 0;
}
return a * b;
}
}
Слабые тесты:
@Test
void testMultiply() {
// ❌ Тесты не проверяют edge cases
assertEquals(6, calculator.multiply(2, 3));
}
PIT найдёт мутации, которые не выявят тесты:
Mutation 1: return 0 → return 1
Status: SURVIVED (тест не проверяет этот случай)
Mutation 2: a * b → a / b
Status: SURVIVED
Mutation 3: a == 0 → a >= 0
Status: SURVIVED
Улучшенные тесты:
@Test
void testMultiply() {
assertEquals(6, calculator.multiply(2, 3));
}
@Test
void testMultiplyByZero_First() {
assertEquals(0, calculator.multiply(0, 5));
}
@Test
void testMultiplyByZero_Second() {
assertEquals(0, calculator.multiply(5, 0));
}
@Test
void testMultiplyNegative() {
assertEquals(-6, calculator.multiply(-2, 3));
assertEquals(-6, calculator.multiply(2, -3));
}
Теперь большинство мутаций будут "убиты".
Metrics Mutation Testing
Mutation Score:
Mutation Score = (Killed Mutations / Total Mutations) * 100%
Пример:
- Total Mutations: 50
- Killed: 40
- Survived: 10
- Mutation Score: 40/50 = 80%
Цель — 80-90% mutation score.
Интеграция в CI/CD
# GitHub Actions
name: Mutation Testing
on: [push, pull_request]
jobs:
mutation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v2
- name: Run mutation tests
run: mvn test-compile org.pitest:pitest-maven:mutationCoverage
- name: Check mutation score
run: |
if grep -q "Score: [0-6][0-9]" target/pit-reports/*.html; then
echo "Mutation score too low!"
exit 1
fi
Best Practices
1. Используй Mutation Testing дополнительно к Code Coverage
Code Coverage 80% + Mutation Score 80% = хорошее качество
Code Coverage 100% + Mutation Score 30% = плохое качество (слабые тесты)
2. Не пытайся убить ВСЕ мутации
Некоторые мутации могут быть эквивалентны (equivalent mutations):
// Исходный код
int x = 5;
// Эквивалентная мутация (тесты никогда не выявят)
int x = 5; // то же самое
3. Сосредоточься на критичном коде
// ✅ Приоритет: бизнес-логика
public int calculateTax(int income) { }
// ⬜ Низкий приоритет: getters, setters
public int getAge() { return age; }
4. Используй аннотации для исключения
// PIT игнорирует некритичные методы
@Generated // Сгенерированный код
public String toString() { }
Сравнение тестирования
Unit Testing (JUnit):
- Проверяет, что код работает
- Code coverage 80% может быть недостаточным
Mutation Testing (PIT):
- Проверяет, что тесты хороши
- Выявляет слабые точки в тестах
- Гарантирует качество
Mutation Testing — это мощный инструмент для повышения качества тестов и уверенности в correctness приложения.