PHP Academy · Lektion

Arbejdsgangen i testdrevet udvikling

Lad designet styres af red-grøn-refaktorér-cyklussen.

Lektion 1 af 413 trin

Arbejdsgangen i testdrevet udvikling er en gratis PHP Academy-lektion på CoddyKit. Dette er lektion 1 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i PHP Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. PHP Academy-kurset indeholder 4 lektioner i alt.

TDD er et værktøj til udformning

Testdrevet udvikling sælges ofte som en testteknik, men dens egentlige værdi er pres på udformningen. Når du skriver testen først, tvinges du til at definere et objekts offentlige grænseflade, dets afhængigheder og dets kontrakt, før du skriver nogen implementering. Testene er et biprodukt; bedre udformning er produktet.

Rød, grøn, refaktorér

Cyklussen har tre strenge faser:

  • Rød — skriv en test, der fejler, for den næste lille adfærd. Den skal fejle af den rigtige grund.
  • Grøn — skriv den mindst mulige kode, der får testen til at bestå, selv hvis den er grim.
  • Refaktorér — ryd op i implementeringen og testene, mens alt stadig er grønt.

Disciplin er vigtig: Spring aldrig Rød over (du kan komme til at teste ingenting), og byg aldrig for meget i Grøn.

Rød: Skriv den fejlslagne test først

Vi bygger en PriceCalculator, der anvender en procentuel rabat. Start med en test af en adfærd, der endnu ikke findes. Når du kører den, skal den fejle, fordi klassen ikke er defineret — det er en gyldig Rød.

<?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));
    }
}

Grøn: Minimal kode, der består

Skriv nu den mindste implementering, der gør testen grøn. Modstå fristelsen til at tilføje afrundingsregler, validering eller håndtering af valuta — der er endnu ingen fejlslagen test, som kræver dem.

<?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)

Triangulering driver generalisering

Én test kan tilfredsstilles med hardcoding. Triangulering — hvor du tilføjer et andet, forskelligt eksempel — tvinger implementeringen til at generalisere. Tilføj et tilfælde, som den trivielle løsning ikke kan snyde sig igennem, så den rigtige formel bliver nødvendig.

<?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));
    }
}

Brug randtilfælde som specifikationer

I TDD bliver et nyt krav til en ny test, der fejler. Antag, at negative rabatter er ugyldige. Skriv først testen, der forventer en undtagelse; den fejler, fordi intet kaster en undtagelse endnu. Testen dokumenterer kontrakten.

<?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);
    }
}

Grøn igen, derefter refaktorér

Tilføj kontrollen, så den nye test består, og refaktorér derefter med tillid — de eksisterende tests opdager tilbagefald. Bemærk, at vi også først indførte afrunding, da en test (eller et kendt krav) begrundede det.

<?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)

Dataleverandører samler eksempler

Når en adfærd er stabil, kan du samle mange eksempelpar i én parametriseret test med en dataleverandør. Det holder Rød/grøn-cyklussen hurtig og gør intentionen let at læse i tabelform.

<?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],
        ];
    }
}

Hold cyklussen hurtig

TDD fungerer kun, hvis feedbacksløjfen tager sekunder, ikke minutter. Praktiske greb:

  • Kør en enkelt fil eller et filter: phpunit --filter test_full_discount_is_free.
  • Brug --testdox til at læse tests som en specifikation af adfærd.
  • Hold enhedstests fri for I/O — ingen database, netværk eller filsystem i den indre cyklus.
vendor/bin/phpunit --testdox --filter PriceCalculator

Refaktorér også testene

Refaktorér-fasen omfatter testsuiten, ikke kun produktionskoden. Fjern duplikering med hjælpefunktioner og setUp(), navngiv tests efter adfærd, og slet tests, der ikke længere kontrollerer noget meningsfuldt. Tests er kode, som du vedligeholder for altid — behandl dem med samme omhu.

<?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 former afhængigheder

Fordi du skriver testen først, bliver uhensigtsmæssige afhængigheder straks tydelige. Hvis en klasse er svær at instantiere i en test, er det feedback om udformningen: injicér samarbejdspartnere i stedet for at oprette dem inde i klassen med new. Rabatberegneren nedenfor bliver testbar ved at modtage sin afrundingsstrategi i stedet for at hardcode den — TDD tvang dette abstraktionspunkt frem.

<?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.

Hurtigt tjek

Hvad er formålet med trinnet Rød?

Opsummering

Du har øvet TDD som en disciplin for udformning:

  • Rød-grøn-refaktorering: test, der fejler, minimal kode og derefter oprydning, mens alt er grønt.
  • Triangulering tvinger generalisering frem; nye krav kommer som nye tests, der fejler.
  • Dataleverandører samler stabile tilfælde; --filter/--testdox holder cyklussen hurtig.
  • Refaktorér også dine tests — de er kode, der lever længe.

Næste emne: fleksible testsubstitutter med Mockery.

Gratis at komme i gang

Lær PHP med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
49
Lektioner
195

Ofte stillede spørgsmål

Er lektionen “Arbejdsgangen i testdrevet udvikling” gratis?

Ja — hele teksten til “Arbejdsgangen i testdrevet udvikling” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af PHP Academy-kurset, skal du opgradere til CoddyKit PRO. PHP Academy-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Arbejdsgangen i testdrevet udvikling”?

Lad designet styres af red-grøn-refaktorér-cyklussen. Du øver dig i PHP Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på PHP Academy?

Der kræves ingen tidligere erfaring. PHP Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 1 af 4.

Hvor lang tid tager lektionen “Arbejdsgangen i testdrevet udvikling”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne PHP Academy-lektion?

Ja. Alle PHP Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Arbejdsgangen i testdrevet udvikling
  2. Mocking og stubbing med Mockery
  3. Integrationstest og funktionel test
  4. Mutationstest med Infection
← Tilbage til PHP Academy