Przepływ pracy wytwarzania sterowanego testami
Projektuj rozwiązanie zgodnie z cyklem red-green-refactor
Przepływ pracy wytwarzania sterowanego testami to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 1 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.
TDD jako narzędzie projektowe
Programowanie sterowane testami jest często przedstawiane jako technika testowania, ale jego prawdziwą wartością jest presja projektowa. Pisanie testu w pierwszej kolejności wymusza zdefiniowanie publicznego interfejsu obiektu, jego zależności i kontraktu, zanim powstanie jakakolwiek implementacja. Testy są produktem ubocznym; lepszy projekt jest właściwym rezultatem.
Red, Green, Refactor
Cykl składa się z trzech ścisłych faz:
- Red — należy napisać nieprzechodzący test dla następnego niewielkiego zachowania. Test musi kończyć się niepowodzeniem z właściwego powodu.
- Green — należy napisać minimalny kod, który przejdzie test, nawet jeśli będzie nieładny.
- Refactor — należy uporządkować implementację i testy, zachowując ich poprawne działanie.
Dyscyplina ma znaczenie: nie należy pomijać fazy Red, ponieważ można wtedy testować nic, ani tworzyć zbyt rozbudowanej implementacji w fazie Green.
Red: najpierw napisz nieprzechodzący test
Zostanie zbudowany PriceCalculator stosujący rabat procentowy. Należy rozpocząć od testu zachowania, które jeszcze nie istnieje. Uruchomienie testu powinno zakończyć się niepowodzeniem, ponieważ klasa jest niezdefiniowana — to prawidłowy etap Red.
<?php
use PHPUnit\Framework\TestCase;
final class PriceCalculatorTest extends TestCase
{
public function test_applies_a_percentage_discount(): void
{
$calc = new PriceCalculator();
// 100 with 10% off -> 90.0
self::assertSame(90.0, $calc->withDiscount(100.0, 10));
}
}
Green: minimalny kod, który przechodzi test
Teraz należy napisać najmniejszą implementację, która zazieleni test. Trzeba oprzeć się pokusie dodawania reguł zaokrąglania, walidacji czy obsługi walut — nie ma jeszcze nieprzechodzącego testu, który by tego wymagał.
<?php
final class PriceCalculator
{
public function withDiscount(float $amount, float $percent): float
{
return $amount - ($amount * $percent / 100);
}
}
$calc = new PriceCalculator();
var_dump($calc->withDiscount(100.0, 10)); // float(90)
Wymuszanie ogólności przez triangulację
Jeden test można spełnić za pomocą wartości wpisanej na stałe. Triangulacja — dodanie drugiego, innego przykładu — wymusza uogólnienie implementacji. Należy dodać przypadek, którego rozwiązanie trywialne nie może udawać, aby wymusić właściwy wzór.
<?php
use PHPUnit\Framework\TestCase;
final class PriceCalculatorTest extends TestCase
{
public function test_zero_percent_is_unchanged(): void
{
self::assertSame(50.0, (new PriceCalculator())->withDiscount(50.0, 0));
}
public function test_full_discount_is_free(): void
{
self::assertSame(0.0, (new PriceCalculator())->withDiscount(50.0, 100));
}
}
Przypadki brzegowe jako specyfikacja
W TDD nowe wymaganie oznacza nowy nieprzechodzący test. Załóżmy, że ujemne rabaty są niedozwolone. Najpierw należy napisać test oczekujący wyjątku; test kończy się niepowodzeniem, ponieważ nic jeszcze go nie zgłasza. Test dokumentuje kontrakt.
<?php
use PHPUnit\Framework\TestCase;
final class PriceCalculatorValidationTest extends TestCase
{
public function test_rejects_negative_discount(): void
{
$this->expectException(\InvalidArgumentException::class);
(new PriceCalculator())->withDiscount(100.0, -5);
}
}
Ponownie Green, a następnie Refactor
Należy dodać warunek ochronny, aby nowy test przeszedł, a następnie spokojnie przeprowadzić refaktoryzację — istniejące testy wykryją regresje. Warto zauważyć, że zaokrąglanie wprowadzono dopiero wtedy, gdy uzasadnił je test lub znane wymaganie.
<?php
final class PriceCalculator
{
public function withDiscount(float $amount, float $percent): float
{
if ($percent < 0 || $percent > 100) {
throw new \InvalidArgumentException('percent must be 0..100');
}
return round($amount - ($amount * $percent / 100), 2);
}
}
var_dump((new PriceCalculator())->withDiscount(19.99, 15)); // float(16.99)
Data providery skracają przykłady
Gdy zachowanie jest już stabilne, wiele par przykładów można połączyć w jeden test parametryzowany z użyciem data providera. Dzięki temu cykl Red/Green pozostaje szybki, a intencja jest czytelna w tabeli.
<?php
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\DataProvider;
final class DiscountTableTest extends TestCase
{
#[DataProvider('cases')]
public function test_discounts(float $amount, float $pct, float $expected): void
{
self::assertSame($expected, (new PriceCalculator())->withDiscount($amount, $pct));
}
public static function cases(): array
{
return [
'no discount' => [100.0, 0, 100.0],
'ten percent' => [100.0, 10, 90.0],
'free' => [100.0, 100, 0.0],
];
}
}
Utrzymywanie szybkiego cyklu
TDD działa tylko wtedy, gdy pętla informacji zwrotnej trwa sekundy, a nie minuty. Praktyczne sposoby:
- Uruchamianie pojedynczego pliku lub filtra:
phpunit --filter test_full_discount_is_free. - Używanie
--testdox, aby czytać testy jak specyfikację zachowania. - Utrzymywanie testów jednostkowych bez operacji I/O — bez bazy danych, sieci ani systemu plików w wewnętrznym cyklu.
vendor/bin/phpunit --testdox --filter PriceCalculatorRefaktoryzacja testów również
Faza Refactor obejmuje zestaw testów, a nie tylko kod produkcyjny. Należy usuwać powtórzenia za pomocą funkcji pomocniczych i setUp(), nazywać testy zgodnie z zachowaniem oraz usuwać testy, które nie sprawdzają już niczego istotnego. Testy to kod utrzymywany przez cały czas życia projektu — należy traktować je z taką samą troską.
<?php
use PHPUnit\Framework\TestCase;
final class PriceCalculatorTest extends TestCase
{
private PriceCalculator $calc;
protected function setUp(): void
{
$this->calc = new PriceCalculator(); // shared setup, no duplication
}
public function test_ten_percent(): void
{
self::assertSame(90.0, $this->calc->withDiscount(100.0, 10));
}
}
TDD kształtuje zależności
Ponieważ test jest pisany w pierwszej kolejności, niewygodne zależności stają się od razu widoczne. Jeśli klasa jest trudna do utworzenia w teście, jest to informacja zwrotna dotycząca projektu: należy wstrzykiwać współpracujące obiekty zamiast tworzyć je wewnątrz za pomocą new. Przedstawiony niżej kalkulator rabatów staje się testowalny dzięki przyjmowaniu zasad zaokrąglania zamiast ich kodowania na stałe — TDD wymusiło utworzenie tego punktu rozszerzenia.
<?php
interface RoundingPolicy { public function round(float $v): float; }
final class PriceCalculator
{
public function __construct(private RoundingPolicy $rounding) {}
public function withDiscount(float $amount, float $percent): float
{
return $this->rounding->round($amount - ($amount * $percent / 100));
}
}
// Tests inject a deterministic RoundingPolicy; no hidden global behavior.
Szybkie sprawdzenie
Jaki jest cel fazy Red?
Podsumowanie
Przećwiczono TDD jako dyscyplinę projektową:
- Red-Green-Refactor: nieprzechodzący test, minimalny kod, a następnie porządkowanie przy zachowaniu poprawnego działania.
- Triangulacja wymusza ogólność, a nowe wymagania pojawiają się jako nowe nieprzechodzące testy.
- Data providery łączą stabilne przypadki, a
--filter/--testdoxpomagają utrzymać szybki cykl. - Testy również należy refaktoryzować — to kod utrzymywany przez długi czas.
Dalej: elastyczne duble testowe z użyciem Mockery.
Często zadawane pytania
Czy lekcja „Przepływ pracy wytwarzania sterowanego testami” jest bezpłatna?
Tak — pełny tekst „Przepływ pracy wytwarzania sterowanego testami” 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 „Przepływ pracy wytwarzania sterowanego testami”?
Projektuj rozwiązanie zgodnie z cyklem red-green-refactor Ć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 1 z 4.
Ile czasu zajmuje lekcja „Przepływ pracy wytwarzania sterowanego testami”?
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