Kapitola 17 · Praxe · Testování DDD kódu v Symfony

Testování DDD kódu v Symfony

Testování Domain-Driven Design kódu v Symfony v praxi. Unit testy doménové vrstvy, integrační testy s Doctrine, funkční testy API, InMemory repozitáře, testování doménových událostí a architektonické testy s Deptrac.

Autor M. Katuščák
Doba čtení ≈ 30 min
Náročnost pokročilá
Publikováno · Aktualizováno ·
Obsah kapitoly

17.01 Filozofie testování v DDD

Doménová vrstva v DDD nezávisí na frameworku ani na databázi, takže ji lze testovat přímo z PHPUnitu bez bootstrappingu Symfony kernelu. To je hlavní praktický rozdíl proti tradičním vrstveným architekturám, kde jsou unit testy svázané s kontejnerem a kde běh tisíce testů trvá minuty. V DDD běží stejný počet testů v sekundách. Stavební kameny doménové vrstvy – entity, hodnotové objekty, agregáty, doménové události – popisuje kapitola Základní koncepty DDD.

FIG. 18.1-A Testovací pyramida pro DDD aplikaci - poměr a obsah jednotlivých vrstev

Testovací pyramida pro DDD

Testovací pyramida (koncept popularizovaný Mikem Cohnem v knize Succeeding with Agile, 2009 [1]) rozděluje testovací sadu do tří vrstev. Liší se rychlostí, mírou izolace a tím, kolik kódu jeden test pokryje:

17.02 Unit testy doménové vrstvy

Unit testy doménové vrstvy tvoří základ testovací sady DDD aplikace. Pokrývají největší podíl kódu, běží v řádu milisekund a nepotřebují nic jiného než PHPUnit a samotné doménové třídy. Žádný bootstrap Symfony kernelu, žádná databáze, žádné fixtures.

Testování Value Objects

Test value objektu ověřuje tři věci: že neplatný vstup vyhodí odpovídající výjimku, že dvě instance se stejnou hodnotou jsou si rovny přes equals(), a že každá operace vrací novou instanci místo modifikace stávající. Tím je hodnotový objekt pokrytý.

Testování entit

Test entity ověřuje, co entita dělá, ne jak vypadají její fieldy. Volá se veřejná metoda, ověřuje se výsledný stav přes další veřejné metody a u zakázaných operací se očekává konkrétní doménová výjimka. Přístup k privátním vlastnostem přes reflexi je signál, že test sleduje implementaci místo chování.

Testování agregátů

Agregát chrání konzistenci skupiny entit a vydává doménové události. Test agregátu má proto dvě role: ověřit transakční invarianty (pravidla platná pro celý agregát po každé operaci) a zkontrolovat, že správné události byly vydány ve správném pořadí jako vedlejší efekt doménových operací.

17.03 Testování doménových událostí

Doménové události jsou způsob, jak agregát mluví se zbytkem systému. Test proto ověřuje přímo to, co agregát po operaci vydá – typ události, její data a pořadí během jedné transakce. Spoléhat se na vedlejší efekt event dispatcheru je křehké a do unit testu přibírá zbytečnou závislost. Pokud váš systém používá události jako zdroj pravdy, doplňující strategie testování auditovatelnosti a rebuildu projekcí najdete v kapitole Event Sourcing.

17.04 Test doubles a InMemory repozitáře

Test double je obecný název pro náhradu reálné závislosti v testu. PHPUnit a literatura rozlišují čtyři varianty (stub, mock, fake, spy) a v DDD má každá z nich jiný dopad: vede k jinému stylu testu a k jiné odolnosti vůči refaktoringu.

17.05 Integrační testy s Doctrine

Integrační testy odpovídají na otázku, kterou unit testy pokrýt nemohou: zda Doctrine mapování, dotazy repozitářů a transakce skutečně dělají to, co jejich rozhraní slibuje. Spouští se proti reálné databázi – typicky SQLite in-memory pro rychlost, nebo testovací PostgreSQL/MySQL instance pro shodu s produkcí.

17.06 Funkční testy API a kontrolerů

Funkční test prochází celý zásobník: request přijde do kontroleru, projde aplikační vrstvou, dotkne se databáze a vrátí odpověď. Ověřuje se HTTP status kód, tělo (typicky JSON), hlavičky a chování při chybových vstupech. V DDD je to jediná vrstva testů, která ověří, že prezentace s aplikační vrstvou spolu skutečně mluví správně.

17.07 Architektonické testy

Pravidlo, že doménová vrstva nesmí záviset na infrastruktuře ani na aplikační vrstvě, drží jen do první spěchající code review, ve které někdo přidá use Doctrine\ORM\Mapping do entity. Architektonické testy tomu zabraňují technicky: pravidla závislostí jsou popsána deklarativně a porušení padne v CI jako spadlý test, ne až v review.

Deptrac

Deptrac je nástroj od QOSSMIC (dříve sensiolabs-de) pro statickou analýzu závislostí v PHP projektech. Definujete vrstvy (layers) a povolená pravidla závislostí (ruleset). Deptrac analyzuje závislosti v kódu a nahlásí porušení. Spouští se v CI jako součást statické analýzy.

PHP-Arkitect jako alternativa

PHP-Arkitect (phparkitect/phparkitect) je alternativní nástroj pro architektonické testy napsaný v PHP. Na rozdíl od Deptrac s YAML konfigurací používá PHP API pro definici pravidel. To umožňuje typově bezpečnou konfiguraci s podporou IDE. Pravidla se definují jako PHPUnit test, takže výsledky se integrují přímo do testovací sady.

17.08 Code coverage a doporučené postupy

Code coverage měří, jaké procento řádků kódu se při běhu testů provede. Sama metrika nic neříká o kvalitě testů – 100% pokrytí lze dosáhnout testy, které jen volají metody bez assertů. Užitečná je ale opačně: tam, kde je pokrytí nízké, leží kód, který nikdo netestuje. Tam stojí za to se podívat.

Testovací pyramida v DDD funguje díky tomu, že doménová vrstva je čistý PHP bez závislostí na frameworku. Tisíce unit testů proto běží v sekundách. Integrační a funkční testy doplňují pokrytí tam, kde vstupuje infrastruktura, a architektonické testy hlídají, aby tato izolace nezmizela při dalším refaktoringu.

Časté otázky

Jak testovat agregát – unit test s mock repozitářem, nebo integrační test?

Agregát se testuje primárně unit testem – je to čistý PHP bez závislostí na frameworku nebo databázi. Test instancuje agregát, volá jeho metody a ověřuje výsledný stav i vyvolané doménové události. Mock repozitáře se přitom nepotřebuje, protože samotný agregát repozitář nevolá. Integrační test doplňuje pokrytí až na úrovni, kde vstupuje persistence – tedy při ukládání a načítání agregátu. Podrobný rozbor v sekci Unit testy doménové vrstvy.

K čemu slouží InMemory repozitář a kdy ho preferovat před mockem?

InMemory repozitář je plnohodnotná implementace rozhraní repozitáře, která drží agregáty v poli v paměti. Oproti mocku simuluje reálné chování (najít, uložit, počítat), takže testy aplikačních služeb procházejí celý use case věrohodněji. Mock se hodí tam, kde je potřeba ověřit konkrétní interakci – kolikrát byla metoda volána a s jakými argumenty. InMemory repozitář naopak slouží pro ověření výsledku, ne volání. Rozbor variant v sekci Test doubles a InMemory repozitáře.

Jak ověřit, že agregát publikuje správné doménové události?

Po vykonání metody se z agregátu vyčte seznam zaznamenaných událostí (typicky přes releaseDomainEvents()) a testem se ověří jejich typ, pořadí i obsah. Kontroluje se, že agregát vyvolal přesně ty události, které má, a nevyvolal žádné navíc. Pro funkční test lze stejné události zachytávat přes Messenger event bus a ověřit reakce dalších částí systému. Praktický příklad v sekci Testování doménových událostí.

Mají se testovat privátní invarianty agregátu, nebo jen veřejné rozhraní?

Testuje se pouze veřejné rozhraní – chování agregátu přes metody, které se reálně volají z aplikační vrstvy. Privátní invarianty jsou detailem implementace a jejich přímé testování sváže test s konkrétní strukturou kódu, což brání refaktoringu. Dobře navržený test ověřuje, že po sérii veřejných volání je agregát ve validním stavu, vyvolal očekávané události a při porušení pravidla vyhodil konkrétní doménovou výjimku. Detailní rozbor v sekci Unit testy doménové vrstvy.

Co jsou architektonické testy a co kontrolují?

Architektonické testy automaticky ověřují, že kód dodržuje zvolená pravidla struktury – například že doménová vrstva nezávisí na Doctrine, že agregáty nevolají repozitáře přímo, nebo že kontrolery nekomunikují s infrastrukturou. V Symfony se používá nástroj Deptrac, který pravidla popisuje deklarativně v YAML a spouští se jako další testovací sada. Porušení pravidla se projeví jako spadlý test, nikoli až při code review. Rozbor nástrojů a pravidel v sekci Architektonické testy.