11.3 Mi az a TDD? – Red-Green-Refactor ciklus
Áttekintő
Az előző alfejezetekben megtanultunk unit teszteket írni – de a TDD (Test-Driven Development) ennél egy lépéssel tovább megy: először a tesztet írod, aztán a kódot. Ez elsőre furán hangzik, talán értelmetlen időpazarlásnak tűnik. Mégis sok tapasztalt fejlesztő esküszik rá.
Ez az alfejezet nem arról szól, hogy TDD-t „kötelező" használni. Arról szól, hogy megértsd a gondolkodásmódot mögötte – mert akkor is hasznos lesz, ha sosem dolgozol strikt TDD csapatban.
Részletes leírás
Mi a TDD?
A TDD (Test-Driven Development) egy fejlesztési módszertan, ahol a fejlesztés ciklusa így néz ki:
- Írj egy tesztet – ami még megbukik, mert a kód még nem létezik
- Írd meg a minimális kódot – ami átmegy a teszten
- Refaktorálj – javítsd a kódot anélkül, hogy eltörnéd a tesztet
Ezt hívják Red-Green-Refactor ciklusnak.
A Red-Green-Refactor ciklus
🔴 RED – Írj egy bukó tesztet
Mielőtt egyetlen sor implementációs kódot írnál, írsz egy tesztet, ami leírja, milyen viselkedést vársz el.
A teszt természetesen megbukik – a kód még nem létezik. Ez szándékos. A piros állapot azt mondja: „ez a viselkedés még nincs implementálva."
Miért fontos, hogy a teszt tényleg megbukjon?
Mert ha az „üres" kódra is átmegy a teszt, valami baj van – a teszt nem ellenőriz semmit.
🟢 GREEN – Írd meg a legegyszerűbb kódot, ami átmegy
Most írsz kódot – de csak annyit, amennyi kell. Ne tervezd túl. Ne gondolj az összes jövőbeli esetre. A cél: a teszt zölddé váljon.
Ez furán hangzik: „csak ennyit?" Igen. Az egyszerűség szándékos. A TDD-ben a kód mindig a tesztek által vezérelt, nem a feltételezések által.
🔵 REFACTOR – Javítsd a kódot, de ne törd el a tesztet
Most, hogy a kód működik (a teszt zöld), visszanézel rá: tiszta-e? Ismétlődik-e valami? Jól van-e elnevezve?
A refaktorálás után a tesztek továbbra is zöldek kell legyenek. Ha valamelyik elbukik, visszalépés.
TDD demonstráció pszeudokódban
Feladat: írj egy is_palindrome függvényt, ami eldönti, hogy egy szó visszafelé olvasva ugyanaz-e.
1. lépés: RED
TESZT: "racecar" → True
TESZT: "hello" → False
Lefuttatom → MEGBUKIK (is_palindrome nem létezik)
2. lépés: GREEN
function is_palindrome(text):
return text == reverse(text)
Lefuttatom → ÁTMEGY
3. lépés: REFACTOR
Rendben, de mi van ha nagy-kisbetű keverék? "Racecar" → False?
Nem scope ez a teszté, de ha le akarom fedni, ELŐSZÖR új tesztet írok (RED), aztán bővítem a kódot (GREEN).
Miért írjuk előbb a tesztet?
Ez az egyetlen dolog, amin a legtöbb junior megakad: „minek előbb a teszt, ha még nincs kód?"
Mert a teszt megírása kényszerít arra, hogy végiggondold a viselkedést, mielőtt implementálnád.
Ha tesztet írsz egy még nem létező függvényhez, ezeket kell megválaszolnod:
- Mit kap bemenetként?
- Mit ad vissza?
- Mi az elvárt viselkedés edge case-eknél?
Ez nem elméleti kérdés – ez a design. A TDD-t sokan azért szeretik, mert a tesztek írása jobb API designt eredményez: ha nehéz tesztet írni a kódhoz, az jele, hogy a kód maga is bonyolult, és refaktorálni kell.
Hogyan segít a design gondolkodásban?
Klasszikus helyzet: megírsz egy funkciót, ami „belülről" logikusan van megépítve, de kívülről nehézkes használni. TDD esetén ez hamarabb kiderül, mert a tesztet kívülről írod – a hívó szemszögéből.
Ha a teszt Arrange részébe 20 sor kell, mielőtt meg tudod hívni a függvényt, az jele: a függvénynek túl sok a függősége, vagy a konstruktor bonyolult. Ilyenkor visszamész és egyszerűsítesz.
TDD nem garantál jó kódot – de nehezebb vele rossz kódot írni anélkül, hogy észre ne vennéd.
TDD mítoszok
„TDD lassabb"
Rövidtávon igen – több kódot írsz (teszt + implementáció). Hosszútávon nem: a tesztek megakadályozzák a regressziót, kevesebb manuális tesztelésre van szükség, és a hibák korán derülnek ki.
Realista nézet: TDD lassít az első napon, de gyorsít a harmadik héten.
„TDD nem praktikus valódi projekten"
Valóban vannak helyzetek, ahol nehéz alkalmazni: UI, adatbázis-lekérdezések, hálózati hívások. De a business logikára – ahol a legtöbb bug keletkezik – szinte mindig működik.
Realista nézet: Nem kell mindent TDD-vel írni. A komplex logikára megéri.
„Ha van teszt, nincs szükség TDD-re"
Tesztek írása és TDD két különböző dolog. Ha a kód után írsz tesztet, hajlamos leszel csak az ismert eseteket tesztelni – és a kódod designját nem fogja a teszt befolyásolni.
Realista nézet: Utólag is hasznos tesztet írni. De TDD más minőségű gondolkodást ad.
Mikor érdemes és mikor nem TDD-vel dolgozni?
Érdemes TDD-t használni:
- Komplex business logika (pl. kedvezmény-kalkulátor, jogosultság-ellenőrzés, validáció)
- Bug fix: először írj egy tesztet, ami reprodukálja a hibát → majd javítsd
- Algoritmus implementáció (sorba rendezés, keresés, transzformáció)
Nehézkes vagy nem célszerű TDD:
- UI elemek (gomb kattintás, animáció) – ott e2e teszt a természetesebb eszköz
- Adatbázis-integráció – integrációs tesztre érdemes hagyni
- Exploratory kódolás: amikor nem tudod még, mit akarsz implementálni, hanem „tapogatózol"
- Prototípusok és proof-of-concept kód, amit úgyis ki fogsz dobni
TDD a valóságban – hogyan néz ki egy munkahelyen?
A legtöbb csapat nem 100%-ban TDD. Van ahol a seniorok TDD-vel dolgoznak, juniorok pedig utólag írnak teszteket. Van ahol sprint végi refactoring részeként kerülnek teszt-first ciklus alá a kritikus modulok.
Az interjún fontos tudni:
- Mi a TDD és mi a Red-Green-Refactor ciklus
- Miben különbözik attól, hogy „a kód után tesztet írsz"
- Milyen helyzetekben érdemes és mikor nem
Ez elég. Nem kell TDD-gurának lenned.
Életszerű példák
„A senior azt mondta, próbáljam ki TDD-vel – és nem tudtam, hol kezdjek"
Képzeld el: meg kell írnod egy email-validáló függvényt. A senior azt mondja: „próbáld ki TDD-vel."
Hogyan kezdesz hozzá?
- Nem nyitod meg az implementációs fájlt
- Megnyitod a teszt fájlt
- Leírod az első elvárt viselkedést:
valid_email("user@example.com") → True - Lefuttatod → piros (a függvény nem létezik)
- Létrehozod a függvényt, ami visszaad
True-t (igen, csak ennyit) → zöld - Következő teszt:
valid_email("not-an-email") → False - Piros → implementálod a valódi ellenőrzést → zöld
- Következő teszt:
valid_email("") → False - Piros → kiegészítés → zöld
- Refaktorálod az implementációt, tesztek zöldek maradnak
Eredmény: Minden fontos esetet lefedsz, és a kód pontosan annyit csinál, amennyit kell.
„Talált egy bugot a kódban – TDD-vel fixeltem"
Ez az egyik legjobb TDD-alkalmazás: bug fix esetén először megírod a tesztet, ami reprodukálja a hibát.
Bejelentett bug: ha az input üres string, a függvény kivételt dob ahelyett, hogy False-t adna vissza.
1. Írod a tesztet: valid_email("") → False [PIROS – reprodukálja a bugot]
2. Javítod a kódot: hozzáadsz egy üres string ellenőrzést
3. Lefuttatod a tesztet → ZÖLD
4. Lefuttatod az összes többi tesztet → mind ZÖLD (nem tört el semmi)
5. Kész
Ez a megközelítés garantálja: ugyanaz a bug nem fog visszajönni, mert mostantól van teszt rá.
„A TDD lassú – mondta a kolléga, és nem értette miért csinálja a senior"
A junior kollégád azt látja, hogy a senior kétszer annyit „gépel" (teszt + implementáció). Azt mondja: „ez lassú."
Amit látnia kellene: A senior 3 napos sprintben egyszer sem szakítják félbe azzal, hogy „valami eltört a production-ban, de senki sem tudja melyik commit okozta." Mert a tesztek jelzik, ha valami eltörik.
Melyik gyorsabb valójában?
- Kevesebbet gépelni, de naponta 1 órát manuálisan tesztelni?
- Vagy kicsit többet gépelni, és a CI pipeline elvégzi a tesztelést?
A TDD időbeli haszna csak hosszabb távon látszik – ezért félreértés „lassúnak" látni.
Tesztfeladatok
1. feladat – TDD lépések sorrendbe állítása
Az alábbi lépések összekeveredtek. Állítsd sorrendbe őket a Red-Green-Refactor ciklus szerint:
A) Kódot írsz, ami átmegy a teszten
B) Refaktorálod a kódot, a tesztek zöldek maradnak
C) Írsz egy tesztet, ami megbukik
D) Lefuttatod a tesztet → piros
E) Lefuttatod a tesztet → zöld
F) Lefuttatod az összes tesztet → mind zöld marad
2. feladat – Mítosz azonosítása
Az alábbi állítások közül melyik TDD mítosz és melyik igaz? Indokold meg röviden!
a) „TDD lassabb, mert több kódot kell írni"
b) „TDD-vel jobb kód designt kapunk"
c) „TDD nem működik valódi projekten, csak toy example-ökön"
d) „Ha van teszt, az ugyanaz, mintha TDD-vel dolgoztunk volna"
e) „TDD kényszert ad arra, hogy végiggondold a viselkedést implementáció előtt"
3. feladat – RED állapot azonosítása
Melyik az, ami a „RED" fázishoz tartozik a TDD-ben?
a) A kód jól van megírva és az összes teszt átmegy
b) Megírtál egy tesztet, ami megbukik, mert a kód még nem létezik
c) A refaktorálás után valamelyik teszt eltört
d) A kód átment a teszten, de még nincs refaktorálva
4. feladat – TDD vs. utólagos tesztelés
Mi a különbség a következő két megközelítés között? Melyik a TDD és melyik nem?
Fejlesztő A: Megírja a `calculate_tax` függvényt. Aztán ír hozzá egy tesztet, ami ellenőrzi, hogy 20%-os ÁFA esetén helyes-e az eredmény.
Fejlesztő B: Először megírja a tesztet: "ha 20%-os ÁFA, akkor 1000 Ft-ból 1200 Ft lesz". A teszt megbukik. Ezután implementálja a `calculate_tax` függvényt, amíg a teszt át nem megy.
Melyik a TDD? Mi a különbség a két megközelítés között a kódminőség szempontjából?
5. feladat – Bug fix TDD-vel
Kaptál egy bug reportot: az is_valid_username függvény elfogad felhasználóneveket, amikben szóköz van (pl. "John Doe"), de nem kellene.
Írd le, hogyan oldanád meg TDD-vel (nem kell valódi kód, pszeudokód elég):
- Hogyan néz ki a RED fázis?
- Hogyan néz ki a GREEN fázis?
- Mit csinálsz a REFACTOR fázisban?
6. feladat – Mikor érdemes TDD?
Az alábbi szituációkból melyikben érdemes TDD-t alkalmazni és melyikben nem? Röviden indokold!
a) Komplex kedvezmény-kalkulátor logika (több feltétel, edge case-ek)
b) Gomb hover animáció CSS-ben
c) JWT token validálása (lejárt-e, érvényes-e a signature)
d) Adatbázis migrációs script írása
e) Email-cím formátum ellenőrzés
f) Prototípus, amiről még nem tudod, marad-e a kódbázisban
7. feladat – Pszeudokód TDD ciklus
Írd végig a Red-Green-Refactor ciklust pszeudokódban az alábbi feladatra:
Feladat: is_strong_password(password) függvény, ami visszaad True-t, ha a jelszó:
- Legalább 8 karakter hosszú
- Tartalmaz legalább egy számot
- Tartalmaz legalább egy nagybetűt
Legalább 3 tesztet írj (3 RED-GREEN-REFACTOR ciklus). Minden lépésnél jelöld, piros vagy zöld az állapot.
8. feladat – Design insight
Az alábbi kód tesztelésre felkészítése közben mit vennél észre?
def process_order(order_id):
db = Database.get_connection()
order = db.query(f"SELECT * FROM orders WHERE id = {order_id}")
user = db.query(f"SELECT * FROM users WHERE id = {order['user_id']}")
email_service = EmailService()
email_service.send_confirmation(user['email'], order)
return True
Ha tesztet próbálsz írni ehhez a függvényhez TDD-vel:
- Milyen problémát észlelsz?
- Hogyan változtatnád meg a függvény interfészét, hogy tesztelhető legyen?
9. feladat – Fogalmi kvíz
9a. A TDD Red-Green-Refactor ciklusban mit jelent a „Red"?
- a) A kód teljesítménye csökkent
- b) A megírt teszt megbukik, mert az implementáció még nem létezik
- c) A kód fordítási hibát dob
- d) A refaktorálás eltörte a teszteket
9b. Mi a TDD egyik legfontosabb mellékhatása a kóddesignra nézve?
- a) Kevesebb kódot kell írni
- b) A kód automatikusan gyorsabb lesz
- c) A nehezen tesztelhető kód jelzi, hogy az implementáció is bonyolult
- d) Nincs hatása a kóddesignra, csak a hibák számát csökkenti
9c. Melyik szituációban NEM ajánlott TDD?
- a) Komplex validációs logika
- b) Algoritmus implementáció
- c) UI animáció és CSS effektek
- d) Bug fix reprodukálása
10. feladat – Gondolkodási feladat
A csapatodon az egyik junior azt mondja: „Az a baj a TDD-vel, hogy mire megírod a tesztet, már tudod, mit fog csinálni a kód – szóval minek a teszt?"
Hogyan reagálnál? Milyen érveket hoznál fel amellett, hogy a teszt megírásának van hozzáadott értéke akkor is, ha tudod, mit fog csinálni a kód? Legalább 3 konkrét érvet sorolj fel.