Abstrakt Test Driven Development (TDD) je vývojová metodika, ve které testy vznikají dříve než produkční kód. Cyklus Red-Green-Refactor vede vývojáře k modulárnímu návrhu, vyšší pokryvnosti testů a živé dokumentaci. Empirické studie z Microsoftu, IBM a akademického prostředí potvrzují snížení defektů o 40 až 90 procent při počátečním zpomalení vývoje o 15 až 35 procent. Článek shrnuje principy TDD, popisuje pokročilé techniky včetně mockování, property-based testingu a mutation testingu a uvádí typické anti-patterns spolu s jejich řešením.
1. Proč psát testy jako první
Tradiční přístup, kdy se testy přidávají až po dokončení implementace, vede k několika systémovým problémům. Testy bývají vázané na konkrétní implementaci, jsou křehké a komplikované kvůli pevně zadrátovaným závislostem. Pokud se kód napíše bez ohledu na testovatelnost, je následné psaní testů náročné a často skončí u testů povrchových.
TDD obrací pořadí. Vývojář nejprve formuluje očekávané chování formou testu, který logicky selže, protože implementace ještě neexistuje. Teprve pak píše minimální kód k jeho splnění. Tento přístup nutí přemýšlet o veřejném rozhraní komponenty dříve, než se napíše první řádek vlastní logiky. Výsledkem je čistější návrh s nízkou vázaností a vysokou soudržností.
2. Cyklus Red-Green-Refactor
Základem TDD je krátký iterativní cyklus o třech krocích. Ve fázi Red vývojář napíše nový test pro chování, které dosud neexistuje, a ověří, že selže ze správného důvodu. Ve fázi Green provede nejmenší možnou změnu, která test rozsvítí zeleně. Hardcoded hodnoty jsou v této fázi přijatelné jako mezistupeň. Ve fázi Refactor se kód vyčistí, odstraní duplicita a zlepší se pojmenování, přičemž testy hlídají, že se chování nezměnilo.
Délka jedné iterace se obvykle pohybuje v jednotkách minut. Kratší cyklus znamená rychlejší zpětnou vazbu a menší riziko, že se vývojář vydá špatným směrem.
3. Pyramida testů a její varianty
Mike Cohn definoval testovací pyramidu se třemi úrovněmi. Jednotkové testy tvoří širokou základnu, jsou rychlé a izolované. Integrační testy jsou méně početné, ověřují spolupráci komponent. Vrchol tvoří end-to-end testy, kterých je nejméně, jsou pomalé, ale ověřují skutečné scénáře z pohledu uživatele.
V mikroslužbové architektuře se prosazuje varianta nazývaná diamant, která zachovává širokou základnu jednotkových testů, ale významně rozšiřuje vrstvu integračních testů. Důvodem je, že významná část logiky se odehrává na hranicích služeb, které je nutné ověřit kontraktovým testováním.
4. Test doubles a mockování
V testech se používá pět typů náhrad. Dummy je objekt, který se předává jako parametr, ale nikdy se nepoužije. Stub vrací předem nastavené hodnoty. Spy zaznamenává, jak byl volán. Mock obsahuje předdefinovaná očekávání a sám ověřuje jejich splnění. Fake je funkční implementace zjednodušená pro testovací účely, typicky in-memory databáze.
Volba správného typu náhrady ovlivňuje čitelnost i odolnost testů. Příliš mnoho mocků znamená, že se test stává zrcadlem implementace, a každá změna kódu si vynutí přepsání testu. Doporučuje se mockovat pouze externí závislosti a pro vlastní kód používat skutečné objekty.
5. Pokročilé techniky
Property-based testing nezadává konkrétní vstupy, ale vlastnosti, které musí platit pro všechny vstupy z definované domény. Knihovna automaticky generuje stovky případů včetně okrajových hodnot. Pro řadicí algoritmus lze formulovat invariant, že výstupní seznam je seřazený a obsahuje stejné prvky jako vstup.
Mutation testing měří kvalitu testů tím, že systematicky modifikuje produkční kód (například změní operátor >= na >) a sleduje, zda alespoň jeden test selže. Pokud projdou všechny testy i u zmutovaného kódu, jde o známku, že některé hraniční případy nejsou ověřeny.
6. Principy FIRST a struktura testu
Dobré testy splňují pět vlastností shrnutých zkratkou FIRST. Jsou rychlé, aby je vývojář spouštěl často. Nezávislé, aby pořadí běhu nehrálo roli. Opakovatelné, takže neřeší aktuální čas ani síťovou dostupnost. Sebeověřující, s jasným verdiktem ano nebo ne. A psané včas, tedy před produkčním kódem.
Vnitřní strukturu testu nejlépe vystihuje vzor Arrange-Act-Assert. Příprava vstupů a kontextu, provedení testované akce a ověření výsledku. Tato třídílná struktura usnadňuje čtení i hledání chyb v selhávajících testech.
7. TDD a clean architecture
Princip dependency inversion vyžaduje, aby vyšší vrstvy nezáviseli na implementacích nižších vrstev, ale na abstrakcích. TDD k tomuto stylu přirozeně vede, protože testovatelnost vyžaduje injektovatelné závislosti. Doménová logika tak zůstává oddělená od infrastruktury, což zlepšuje udržovatelnost i možnost paralelního vývoje.
Use case vrstva s business logikou se testuje pouze proti rozhraním repozitářů a externích služeb, které se v testech nahrazují fake nebo mock implementacemi. Skutečné databázové dotazy se ověřují v integračních testech jiné úrovně.
8. Anti-patterns a jejich řešení
Mezi nejčastější chyby patří testování implementace místo chování. Test, který ověřuje konkrétní SQL dotaz, selže při legitimní optimalizaci, přestože se výsledek nezměnil. Lepší je ověřit, že metoda vrací jen aktivní uživatele seřazené podle data.
Další chybou jsou nicneříkající názvy testů, jako jsou test1 nebo should work. Název má popisovat ověřované chování. Velké testy ověřující celý workflow naráz se hůře udržují než série menších cílených testů. Křehké testy závislé na pořadí dat selhávají při každé změně testovacích fixtur.
9. Empirické studie a návratnost investice
Studie společnosti Microsoft z roku 2008 sledovala čtyři týmy a zjistila pokles defektů o 40 až 90 procent při zvýšení doby vývoje o 15 až 35 procent. IBM v dlouhodobé studii potvrdilo 40procentní snížení defektů a kladnou návratnost po druhém vydání produktu. Akademická studie z Mnichova prokázala signifikantně lepší interní kvalitu kódu a vyšší jistotu vývojářů, byť bez prokazatelného dopadu na produktivitu v krátkém horizontu.
Z manažerského pohledu představuje TDD investici do snížení nákladů na opravy v pozdějších fázích. Defekt zachycený v testovací fázi stojí násobně méně než defekt v produkci.
10. Integrace do CI/CD
V pipeline by měla být fáze unit testů povinná pro každý commit. Mutation testing a měření pokrytí jsou vhodné pro pull requesty. Pre-commit hook spouštějící lokální testy zabrání odeslání rozbitého kódu. Hranice pokrytí, typicky 80 procent, slouží jako kvalitativní brána, ale neměla by být cílem sama o sobě. Sto procent pokrytých řádků nezaručuje sto procent ověřeného chování.
Závěr
TDD je nástroj návrhu, nikoli pouze nástroj testování. Cyklus Red-Green-Refactor vede k modulárnímu kódu s nízkou vázaností a živé dokumentaci, kterou tvoří samotná testovací sada. Po počátečním zpomalení během prvních týdnů adopce přichází zrychlení vývoje díky důvěře v automatizovanou regresi.
Klíčem k úspěšnému zavedení je pozvolný start na novém modulu nebo zelené louce, párové programování pro přenos znalostí v týmu a měření smysluplných metrik, jako jsou hustota defektů, mutation score a doba zpětné vazby. TDD vyžaduje disciplínu, ale výsledky v podobě udržitelného, refaktorovatelného kódu investici dlouhodobě vrátí.
Reference:
- Beck, K. (2002): Test Driven Development: By Example
- Freeman, S. & Pryce, N. (2009): Growing Object-Oriented Software, Guided by Tests
- Feathers, M. (2004): Working Effectively with Legacy Code
- Meszaros, G. (2007): xUnit Test Patterns
- Fowler, M.: Test Driven Development - https://martinfowler.com/bliki/TestDrivenDevelopment.html