Testy mutacyjne za pomocą Infection
Sprawdź, jak dobre są naprawdę Twoje testy
Testy mutacyjne za pomocą Infection to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.
Pokrycie kodu bywa mylące
Pokrycie kodu na poziomie 100% wydaje się uspokajające, ale dowodzi tylko, że testy wykonały kod — nie tego, że wykryłyby błąd. Test może wykonać daną linię i nie sprawdzić niczego istotnego. Testy mutacyjne mierzą rzeczywistą skuteczność: czy testy zakończyłyby się niepowodzeniem, gdyby kod został subtelnie zepsuty? Infection to standardowe narzędzie PHP do tego celu.
composer require --dev infection/infectionGłówna idea: mutanty
Infection pobiera kod objęty pokryciem i wprowadza do niego drobne usterki — mutanty. Zamienia > na >=, + na -, && na ||, usuwa return itd. Następnie ponownie uruchamia testy dla każdego mutanta:
- Jeśli test zakończy się niepowodzeniem → mutant zostaje zabity (dobrze — testy wykryły usterkę).
- Jeśli wszystkie testy przejdą pomyślnie → mutant przetrwał (źle — rzeczywisty błąd w tym miejscu pozostałby niezauważony).
Mutant, który przetrwał
Rozważmy tę funkcję i słaby test. Infection zmieniłby >= na >. Jeśli żaden test nie sprawdza dokładnej granicy (gdy kwota jest równa progowi), mutant przetrwa — ujawniając nieprzetestowany przypadek brzegowy.
<?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
Zabijanie mutanta
Dodanie asercji granicznej powoduje, że mutant zmieniający >= na > zostaje zabity: w zmodyfikowanym kodzie 50.0 > 50.0 ma wartość false, więc test kończy się niepowodzeniem — dokładnie tego oczekujemy. Testy mutacyjne dosłownie wskazują, których asercji brakuje.
<?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));
}
}
Konfiguracja: infection.json
Działanie Infection jest sterowane przez infection.json5. Należy określić katalogi, w których mają być wprowadzane mutacje, miejsce zapisu logów oraz minimalne progi wyników wymagane do pomyślnego przejścia CI. Wartość source.directories powinna wskazywać wyłącznie kod produkcyjny — nigdy testy.
{
"source": {
"directories": ["src"]
},
"logs": {
"text": "build/infection.log",
"html": "build/infection.html"
},
"mutators": {
"@default": true
},
"minMsi": 80,
"minCoveredMsi": 90
}Metryki MSI
Infection raportuje wskaźnik wyniku mutacji:
- MSI = zabite mutanty / wszystkie mutanty. Obniżają go fragmenty nieobjęte pokryciem (tych mutantów nie można zabić).
- Covered MSI = zabite mutanty / mutanty w pokrytych wierszach. Mierzy jakość asercji w miejscach, które są testowane.
- Mutation Code Coverage = informacja o tym, jak dużą część kodu Infection w ogóle mogło zmodyfikować.
Wysokie pokrycie linii przy niskim Covered MSI to klasyczny sygnał testów z niewystarczającymi asercjami.
Wydajne uruchamianie Infection
Testy mutacyjne są kosztowne — zestaw testów jest uruchamiany ponownie dla każdego mutanta. Dwa duże sposoby na przyspieszenie to równoległe uruchamianie testów za pomocą --threads oraz modyfikowanie tylko kodu dotkniętego zmianami w bieżącej gałęzi, z użyciem filtrowania różnic Git. To idealne rozwiązanie dla CI w pull requestach.
vendor/bin/infection --threads=max --git-diff-lines --git-diff-base=origin/mainAnaliza mutantów, które przetrwały
Największa wartość tkwi w diffie wyświetlanym przez Infection dla każdego mutanta, który przetrwał. Pokazuje on dokładną linię oraz zmianę, której testy nie wykryły. Należy traktować przetrwałe mutanty jako listę zadań: dodać brakującą asercję albo uznać, że mutant jest nieszkodliwy (mutant równoważny).
- return $total >= 50.0;
+ return $total > 50.0;
# Mutant survived: no test asserts the inclusive boundary (total === 50.0)Mutanty równoważne i ich pomijanie
Niektóre mutanty są równoważne — zmieniają kod, ale nie jego obserwowalne zachowanie, więc żaden test nie mógłby ich zabić. Nie można ich zabić, a niesprawiedliwie obniżają MSI. Zamiast zmieniać testy tylko po to, by je wykryć, należy wyciszać znane mutatory dające fałszywie pozytywne wyniki dla konkretnego kodu.
{
"mutators": {
"@default": true,
"Plus": {
"ignore": ["App\\Math\\Statistics::variance"]
}
}
}Gdzie testy mutacyjne przynoszą korzyści
Ponieważ testy mutacyjne są wolne, należy dobrze wybierać ich zakres:
- Stosować je do kluczowej logiki domenowej — wyceny, uprawnień i obliczeń — gdzie ciche błędy są kosztowne.
- Wymagać weryfikacji PR-ów na podstawie Covered MSI dla zmienionych wierszy, a nie dla całego repozytorium.
- Nie dążyć do globalnego wyniku 100%; malejące korzyści i mutanty równoważne sprawiają, że byłoby to marnotrawstwem.
- Używać testów mutacyjnych do znajdowania słabych asercji, a następnie poprawiać testy — wynik jest środkiem, nie celem.
Dane o pokryciu przyspieszają działanie
Infection modyfikuje tylko te wiersze, które testy faktycznie pokrywają, dlatego ponownie wykorzystuje dane o pokryciu z narzędzia uruchamiającego testy. W przypadku Xdebug jest to wolne; pcov jest zdecydowanie szybszy przy obliczaniu pokrycia linii i jest zalecanym sterownikiem do uruchamiania testów mutacyjnych. Infection może również samodzielnie wygenerować dane o pokryciu albo wykorzystać raport pokrycia utworzony wcześniej w CI.
# 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-testsSzybkie sprawdzenie
Co oznacza mutant, który przetrwał?
Podsumowanie
Nauczyli się Państwo mierzyć jakość testów, a nie tylko ich liczbę:
- Testy mutacyjne wprowadzają drobne usterki (mutanty); zabity mutant oznacza wykrytą usterkę, a przetrwały — lukę.
- Pokrycie pokazuje wykonanie kodu, a Covered MSI — siłę asercji.
- Konfigurację określa się w
infection.json5za pomocą progów MSI; dla zwiększenia szybkości można używać--threadsi filtrowania różnic Git. - Przetrwałe mutanty tworzą listę zadań; należy zwracać uwagę na mutanty równoważne i świadomie je pomijać.
- Testy należy kierować na kluczową logikę, a PR-y weryfikować na podstawie MSI zmienionych wierszy, zamiast dążyć do globalnego wyniku 100%.
Często zadawane pytania
Czy lekcja „Testy mutacyjne za pomocą Infection” jest bezpłatna?
Tak — pełny tekst „Testy mutacyjne za pomocą Infection” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Testy mutacyjne za pomocą Infection”?
Sprawdź, jak dobre są naprawdę Twoje testy Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć PHP Academy?
Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Testy mutacyjne za pomocą Infection”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?
Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Przepływ pracy wytwarzania sterowanego testami
- Mockowanie i stubowanie za pomocą Mockery
- Testy integracyjne i funkcjonalne
- Testy mutacyjne za pomocą Infection