← Назад к вопросам

Что такое Mutation Testing?

2.4 Senior🔥 151 комментариев
#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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 приложения.