0Pricing
PHP Academy · Lekcja

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 PriceCalculator

Refaktoryzacja 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/--testdox pomagają 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

  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