Мутационное тестирование с Infection
Оценивайте, насколько хороши Ваши тесты на самом деле
«Мутационное тестирование с 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 — локальная установка не требуется.
Все уроки этого курса
- Рабочий процесс разработки через тестирование
- Мокирование и заглушки с Mockery
- Интеграционное и функциональное тестирование
- Мутационное тестирование с Infection