11.3 Mi az a TDD

← 11. TDD – Automatizált tesztek

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:

  1. Írj egy tesztet – ami még megbukik, mert a kód még nem létezik
  2. Írd meg a minimális kódot – ami átmegy a teszten
  3. 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á?

  1. Nem nyitod meg az implementációs fájlt
  2. Megnyitod a teszt fájlt
  3. Leírod az első elvárt viselkedést: valid_email("user@example.com") → True
  4. Lefuttatod → piros (a függvény nem létezik)
  5. Létrehozod a függvényt, ami visszaad True-t (igen, csak ennyit) → zöld
  6. Következő teszt: valid_email("not-an-email") → False
  7. Piros → implementálod a valódi ellenőrzést → zöld
  8. Következő teszt: valid_email("") → False
  9. Piros → kiegészítés → zöld
  10. 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):

  1. Hogyan néz ki a RED fázis?
  2. Hogyan néz ki a GREEN fázis?
  3. 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.

Scroll to Top