0Pricing
PHP Academy · Lekcja

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/infection

Głó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/main

Analiza 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-tests

Szybkie 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.json5 za pomocą progów MSI; dla zwiększenia szybkości można używać --threads i 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

  1. Przepływ pracy wytwarzania sterowanego testami
  2. Mockowanie i stubowanie za pomocą Mockery
  3. Testy integracyjne i funkcjonalne
  4. Testy mutacyjne za pomocą Infection
← Powrót do PHP Academy