PHP Academy · Урок

Мутационное тестирование с Infection

Оценивайте, насколько хороши Ваши тесты на самом деле

Урок 4 из 413 шагов

«Мутационное тестирование с Infection» — бесплатный урок PHP Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения PHP Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс PHP Academy содержит 4 уроков всего.

Покрытие вводит в заблуждение

Стопроцентное покрытие строк кажется обнадёживающим, но оно доказывает только то, что тесты выполнили код, а не то, что они обнаружили бы ошибку в нём. Тест может выполнить строку и не проверить ничего существенного. Мутационное тестирование измеряет именно это: завершатся ли ваши тесты ошибкой, если код будет слегка повреждён? Infection — стандартный инструмент PHP для такой проверки.

composer require --dev infection/infection

Основная идея: мутанты

Infection берёт покрытый код и вносит в него небольшие дефекты — мутанты. Он заменяет > на >=, + на -, && на ||, удаляет return и так далее. Затем он повторно запускает тесты для каждого мутанта:

  • Если тест завершается ошибкой → мутант убит (хорошо: тесты обнаружили дефект).
  • Если все тесты проходят → мутант выжил (плохо: настоящая ошибка здесь осталась бы незамеченной).

Выживший мутант в действии

Рассмотрим эту функцию и слабый тест. Infection заменит >= на >. Если ни один тест не проверяет точную границу, когда сумма равна порогу, мутант выживает, выявляя непроверенный крайний случай.

<?php
function qualifiesForFreeShipping(float $total): bool
{
    return $total >= 50.0;   // Infection mutates >= to >
}

// Weak test only checks 100 and 10 -> never tests exactly 50.0
var_dump(qualifiesForFreeShipping(100.0)); // true
var_dump(qualifiesForFreeShipping(10.0));  // false
var_dump(qualifiesForFreeShipping(50.0));  // true  <-- the boundary the mutant exposes

Уничтожение мутанта

Добавьте проверку границы, и мутант >=-на-> погибнет: в изменённом коде 50.0 > 50.0 равно false, поэтому тест завершается ошибкой — именно этого мы и хотим. Мутационное тестирование буквально показывает, каких проверок не хватает.

<?php
use PHPUnit\Framework\TestCase;

final class ShippingTest extends TestCase
{
    public function test_threshold_is_inclusive(): void
    {
        // Kills the >= -> > mutant
        self::assertTrue(qualifiesForFreeShipping(50.0));
    }
}

Конфигурация: infection.json

Infection управляется файлом infection.json5. В нём указывается, какие каталоги изменять мутациями, куда сохранять журналы и какие минимальные пороги показателя использовать для успешного прохождения среды непрерывной интеграции. Направьте source.directories только на рабочий код, а не на тесты.

{
  "source": {
    "directories": ["src"]
  },
  "logs": {
    "text": "build/infection.log",
    "html": "build/infection.html"
  },
  "mutators": {
    "@default": true
  },
  "minMsi": 80,
  "minCoveredMsi": 90
}

Метрики MSI

Infection сообщает индикатор мутационного показателя:

  • MSI = убитые мутанты / все мутанты. Непокрытый код снижает показатель, поскольку его мутанты нельзя убить.
  • MSI покрытого кода = убитые мутанты / мутанты на покрытых строках. Показывает, насколько хороши ваши проверки там, где вы действительно тестируете код.
  • Покрытие кода мутациями = доля кода, которую Infection вообще смог изменить.

Высокое покрытие строк при низком MSI покрытого кода — классический признак тестов с недостаточным количеством проверок.

Эффективный запуск Infection

Мутационное тестирование требует больших затрат: набор тестов запускается заново для каждого мутанта. Два главных способа ускорить процесс: запускайте тесты параллельно с --threads и изменяйте только код, затронутый текущей веткой, используя фильтрацию по изменениям Git; это особенно удобно для запросов на слияние в среде непрерывной интеграции.

vendor/bin/infection --threads=max --git-diff-lines --git-diff-base=origin/main

Чтение выживших мутантов

Ценность заключается в изменениях, которые Infection выводит для каждого выжившего мутанта. Они показывают точную строку и изменение, которое тестам не удалось обнаружить. Относитесь к выжившим мутантам как к списку задач: добавьте недостающую проверку или признайте, что мутант безвреден (эквивалентный мутант).

- return $total >= 50.0;
+ return $total > 50.0;

# Mutant survived: no test asserts the inclusive boundary (total === 50.0)

Эквивалентные мутанты и игнорирование

Некоторые мутанты эквивалентны: они изменяют код, но не его наблюдаемое поведение, поэтому ни один тест не смог бы их убить. Их нельзя убить, и они несправедливо снижают MSI. Подавляйте заведомо ложноположительные операторы мутаций в конкретном коде, а не подгоняйте тесты, чтобы охотиться за такими мутантами.

{
  "mutators": {
    "@default": true,
    "Plus": {
      "ignore": ["App\\Math\\Statistics::variance"]
    }
  }
}

Где мутационное тестирование особенно полезно

Поскольку оно медленное, выбирайте цели разумно:

  • Применяйте его к ключевой логике предметной области — расчёту цен, разрешениям, вычислениям, — где незаметные ошибки дорого обходятся.
  • Устанавливайте для запросов на слияние порог MSI покрытого кода для изменённых строк, а не для всего репозитория.
  • Не стремитесь к глобальным 100%: убывающая отдача и эквивалентные мутанты делают такую цель неэффективной.
  • Используйте его, чтобы находить слабые проверки, а затем исправляйте тесты — показатель является средством, а не целью.

Для быстродействия нужны данные о покрытии

Infection изменяет только строки, которые действительно покрывают ваши тесты, поэтому повторно использует данные о покрытии, собранные средством запуска тестов. С Xdebug это медленно; pcov значительно быстрее для покрытия строк и является рекомендуемым драйвером для запусков мутационного тестирования. Infection также может самостоятельно создать данные о покрытии или использовать отчёт о покрытии, уже созданный в среде непрерывной интеграции.

# Faster mutation runs: use pcov instead of Xdebug for coverage
php -d pcov.enabled=1 vendor/bin/infection --threads=max

# Or reuse coverage already generated by your PHPUnit step:
vendor/bin/infection --coverage=build/coverage --skip-initial-tests

Быстрая проверка

Что сообщает выживший мутант?

Итоги

Вы научились измерять качество тестов, а не только их количество:

  • Мутационное тестирование вносит небольшие дефекты (мутанты); убитый мутант означает, что дефект обнаружен, а выживший — пробел в проверках.
  • Покрытие показывает выполнение кода; MSI покрытого кода показывает силу проверок.
  • Настраивайте процесс через infection.json5 с порогами MSI; для ускорения запускайте его с --threads и фильтрацией по изменениям Git.
  • Выжившие мутанты образуют список задач; учитывайте эквивалентные мутанты и намеренно игнорируйте их.
  • Сосредоточьтесь на ключевой логике и устанавливайте для запросов на слияние порог MSI изменённых строк, а не стремитесь к глобальным 100%.
Можно начать бесплатно

Изучай PHP с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
49
Уроки
195

Часто задаваемые вопросы

Урок «Мутационное тестирование с Infection» бесплатный?

Да — полный текст урока «Мутационное тестирование с Infection» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс PHP Academy, подпишись на CoddyKit PRO. Курс PHP Academy содержит 4 уроков всего.

Чему я научусь в уроке «Мутационное тестирование с Infection»?

Оценивайте, насколько хороши Ваши тесты на самом деле Ты практикуешь PHP Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать PHP Academy?

Предыдущий опыт не требуется. PHP Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Мутационное тестирование с Infection»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке PHP Academy?

Да. Каждый урок PHP Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Рабочий процесс разработки через тестирование
  2. Мокирование и заглушки с Mockery
  3. Интеграционное и функциональное тестирование
  4. Мутационное тестирование с Infection
← Назад к PHP Academy