11.5 Test coverage

← 11. TDD – Automatizált tesztek

11.5 Test coverage – mit mér és mit nem

Áttekintő

Van egy szám, ami ijesztően tud kinézni kódfelülvizsgálaton: a coverage-százalék. „Miért csak 67%? Legyen legalább 80%!" – hallod a tech leadtől. Aztán van a másik véglet: a csapat heteket tölt rá, hogy 95%-ról 100%-ra tornázza a coverage-et, miközben a kód tele van bugokkal.

A test coverage hasznos eszköz – de félreértik. Ez az alfejezet megmutatja, mit mér valójában, mit nem lát, és hogyan gondolkodj róla konstruktívan ahelyett, hogy a számot kergesd.


Részletes leírás

Mi az a code coverage?

A code coverage egy mérőszám: a kódod hány százaléka fut le tesztjeid futtatása közben.

Ha van 100 sornyi kódod és a tesztek 70 sort érintenek, a coverage 70%.

Ez egyszerűnek hangzik – és valóban az. De az egyszerűsége egyben a korlátja is.


A coverage típusai

Nem minden coverage egyforma. A legtöbb eszköz többfélét is mér:

Line coverage (sorcoverage)

Az adott kódsor lefutott-e legalább egyszer?

def discount(price, is_member):
    if is_member:          # ← ezt a sort elérte a teszt?
        return price * 0.9
    return price           # ← ezt a sort elérte a teszt?

Ha teszteled az is_member=True esetet, a return price * 0.9 sor le fog futni. Az if is_member: sor is. De a return price sor nem fut le – az a sor nem fedett.

Line coverage: 3/4 sor = 75%

Branch coverage (ágcoverage)

Az összes lehetséges elágazást végigjártad-e?

Az előző példában az if is_member: sornak két ága van: igaz eset és hamis eset. Ha csak az igaz esetet tesztelted, az ág csak felerészt van lefedve.

if is_member:
    ├── True  ✓ (teszteltük)
    └── False ✗ (nem teszteltük)

Branch coverage: 1/2 ág = 50% – annak ellenére, hogy a line coverage 75% volt.

A branch coverage szigorúbb, és általában hasznosabb. Megmutatja, hogy a logikai döntési pontokat lefedted-e.

Function/method coverage

Az adott függvény egyáltalán meghívódott-e? Hasznos gyors áttekintésre, de kevésbé informatív.

Statement coverage

Hasonló a line coverage-hez, de utasításszinten méri (egy sorban lehet több utasítás).


Hogyan néz ki a coverage mérés a gyakorlatban?

A legtöbb modern tesztelési eszköz beépítve tartalmazza:

Python – pytest-cov:

pytest --cov=myapp --cov-report=html

Ez generál egy HTML riportot, ahol minden fájlnál látod, melyik sor van lefedve (zöld) és melyik nem (piros).

JavaScript – Jest:

jest --coverage

PHP – PHPUnit:

phpunit --coverage-html coverage-report/

A riport tipikusan így néz ki egy fájlnál:

app/services/UserService.php   Lines: 82%   Branches: 71%
app/services/PaymentService.php Lines: 45%  Branches: 38%
app/models/User.php            Lines: 91%   Branches: 88%

A PaymentService.php kiugrik: érdemes megnézni, mi nincs lefedve.


Miért nem kell (és nem szabad) 100% coverage?

Ez az egyik leggyakrabban félreértett pont. A 100% coverage nem célállapot. Íme, miért:

1. Diminishing returns – csökkenő hozam

Az első tesztek megírása hatalmas értéket ad: lefed egy csomó kritikus útvonalat. Az utolsó néhány százalék eléréséhez viszont egyre nehezebb kódot kell tesztelni – és az a kód általában triviális vagy rendkívül ritka eset.

Gondolj bele: egy CRUD API 80%-os coverage-nél már teszteli a create, read, update, delete happy path-okat és a legfontosabb hibaeseteket. A maradék 20% lehet getterek, triviális adatátalakítások és olyan edge case-ek, amikre 1 év alatt nem kerül sor.

2. A coverage nem méri a teszt minőségét

Ez a legfontosabb pont: magas coverage nem jelent jó teszteket.

Nézd meg ezt a példát:

def calculate_tax(price, rate):
    return price * rate

def test_calculate_tax():
    result = calculate_tax(100, 0.27)
    assert result is not None  # ← ez egy ROSSZ teszt

A függvény le fog futni, a coverage 100% lesz – de a teszt semmit nem ellenőriz érdemben. Ha holnap valaki véletlenül price + rate-re változtatja a kódot, ez a teszt nem fog elbukni.

100% coverage, 0% hasznos visszajelzés.

3. Néhány kód nem érdemes tesztelni

  • Trivially simple getterek/setterek
  • Generált kód (pl. ORM migration-ök)
  • Third-party library hívások (azokat nem mi teszteljük)
  • Konfigurációs fájlok

Ha ezeket is beleszámolod a coverage-be, akkor „olcsó" coverage-et nyersz, ami torzítja a képet.


Coverage mint mutató, nem cél

A helyes gondolkodásmód: a coverage riport egy térkép, nem egy céltábla.

Ahogy egy térkép megmutatja, hol jártál és hol nem, a coverage riport megmutatja:

  • Melyek azok a kódrészletek, amiket egyetlen teszt sem érint?
  • Melyik elágazást soha nem teszteltük?

Ez értékes információ! Különösen akkor, ha:

  • Bugot találtál, és megnézed: az adott kódsor le volt-e fedve? (Ha nem, most tudod, mi hiányzott.)
  • Új fejlesztő érkezik, és meg akarja érteni, milyen tesztfedettség van.
  • Kockázatelemzésnél meg akarod nézni: a kritikus üzleti logikát lefedik-e a tesztek?

Ne ebből indulj ki: „Elértem a 80%-ot, tehát kész vagyok." Ehelyett: „Megnézem, mi nincs lefedve, és eldöntem, érdemes-e tesztet írni rá."


Hogyan azonosítsd, mi hiányzik ténylegesen?

A coverage riporton túl ezekre figyelj:

1. Hibakezelési útvonalak

A happy path általában le van fedve. A hibaesetek kevésbé.

def load_config(path):
    try:
        with open(path) as f:         # ← happy path: tesztelve
            return json.load(f)
    except FileNotFoundError:          # ← ez a branch: tesztelve?
        return {}
    except json.JSONDecodeError:       # ← ez a branch: tesztelve?
        raise ValueError("...")

2. Boundary értékek

Ha egy függvény 1-10 közötti számot vár, tesztelted-e a 0-t, az 1-et, a 10-et, a 11-et?

3. Kombinációk

Több paraméter esetén az összes kombináció exponenciálisan nő. Nem kell mindet tesztelni, de az értelmes kombinációkat igen.

4. Ritka, de kritikus útvonalak

Amit ritkán érsz el, de ha mégis, az komoly következményekkel jár (fizetési hibakezelés, adattörlés).


Reális coverage elvárások

Általánosan elfogadott iparági hüvelykujjszabályok:

RétegElvárható coverage
Business logic / services80–90%
Utility / helper függvények70–85%
Controllers / route handlers60–75%
Infrastruktúra, konfigurációs kód30–50% (vagy kevesebb)
Teljes projekt összesítve60–80%

Ezek nem kőbe vésett szabályok, hanem kiindulópontok. Egy fizetési rendszer kritikus logikájánál 90% alja, egy admin utility scriptnél 40% is elfogadható.


Életszerű példák

1. A „zöld coverage" hamis biztonság-érzet

Gábor örömmel jelenti a code review-n: „95% coverage van, mehetünk!" A reviewer megnézi a teszteket, és látja: a tesztek fele csak azt ellenőrzi, hogy a függvény nem dob exception-t. Nincs egyetlen assert sem az üzleti logikára.

A deployment után bugok kerülnek elő, mert az if is_premium_user ág hibásan volt implementálva – és a tesztek nem ellenőrizték a visszatérési értéket.

Tanulság: Nézd meg a teszteket, ne csak a százalékot.


2. A coverage riport mint bug-térkép

Eszter junior fejlesztőként csatlakozik egy csapathoz. Megkéri a tech leadet, hogy futtassa a coverage riportot. Azonnal kiugrik, hogy a PaymentProcessor.handleRefund() metódus 0% branch coverage-szel rendelkezik.

Megkérdezi: „Ez szándékos?" A válasz: „Nem, ez egy régóta ismert lyuk, csak soha nem volt kapacitás megírni."

Eszter megírja a teszt eseteket – és az egyikben talál egy éles bugot: bizonyos összeg felett a visszatérítés negatívba ment.

Tanulság: A coverage riport megmutatja, hol érdemes kézzel is átnézni.


3. A 100%-os coverage-vadász

Dániel csapatán kötelező a 80% coverage CI-ban. Dániel azonban perfekcionista, és 98%-ra akarja felvinni. Két napot tölt triviális getter-tesztek megírásával:

def test_user_id_getter():
    user = User(id=1, name="Test")
    assert user.id == 1  # ← Ez semmit nem véd meg

def test_user_name_getter():
    user = User(id=1, name="Test")
    assert user.name == "Test"  # ← Ez sem

Ezalatt a kolléga egy kritikus auth middleware logikát ír tesztek nélkül, mert „majd visszajövök rá" – és nem jön vissza.

Tanulság: A coverage-szám kergetése elvonja a figyelmet a valóban kockázatos kódrészletekről.


4. Branch coverage mint vita tárgya

Anna PR-t nyit, a CI zölden fut, 82% coverage. A reviewer jelzi: „Branch coverage csak 61% – miért?"

Anna megnézi: az if user.is_admin ág soha nem lett tesztelve a False esettel. Az admin check egy biztonsági gate – ha rosszul működik, mindenki adminná válhat.

Ez a teszthiány nem szándékos volt, csak figyelmen kívül maradt. Megírja a negatív tesztet, a branch coverage 78%-ra ugrik, és a logikai hibát megtalálják tesztelés közben.

Tanulság: Branch coverage megmutat olyan lyukakat, amit a line coverage elfed.


Tesztfeladatok

1. Melyik állítás igaz a test coverage-ről?

a) Ha a coverage 100%, akkor a kód hibamentes
b) A coverage megmutatja, hogy a tesztek helyesen ellenőrzik-e az üzleti logikát
c) A coverage megmutatja, hogy a kód mely részei futnak le tesztek közben
d) A 80% feletti coverage garantálja, hogy nincs regresszió

Helyes válasz: c)
A coverage csak azt méri, hogy melyik kódsor/ág futott le. Nem méri a tesztek minőségét, és semmit nem garantál a hibamentességről.


2. Mi a különbség a line coverage és a branch coverage között?

a) A line coverage gyorsabb számolni, a branch coverage pontosabb
b) A line coverage soronként nézi, hogy lefutott-e; a branch coverage az összes logikai elágazást nézi
c) A branch coverage csak feltételes utasításokat mér, a line coverage mindent
d) Nincs különbség, ugyanazt mérik más névvel

Helyes válasz: b)
Egy sor 100%-ban lefedett lehet line coverage szempontjából, miközben az adott sorban lévő if egyik ága sosem futott le.


3. Melyik az a coverage-típus, ami megmutatja, hogy az if is_admin: mindkét esetét (igaz és hamis) teszteltük-e?

a) Line coverage
b) Statement coverage
c) Branch coverage
d) Function coverage

Helyes válasz: c)
A branch coverage az elágazások mindkét ágát (igaz/hamis) nyomon követi.


4. Mi a fő probléma ezzel a teszttel, annak ellenére, hogy 100% coverage-et biztosít?

def add(a, b):
    return a + b

def test_add():
    result = add(2, 3)
    assert result is not None

a) A teszt nem ellenőrzi a visszatérési értéket, csak azt, hogy nem None
b) A tesztnek több paramétert kellene tesztelnie
c) A függvény neve nem megfelelő
d) A teszt túl egyszerű, legalább 5 assertet kellene tartalmaznia

Helyes válasz: a)
A assert result is not None sosem bukik el, mert az add sosem ad vissza None-t. A helyes assert: assert result == 5. A coverage 100%, a teszt értéktelen.


5. Egy csapat 45%-os coverage-gel rendelkezik a PaymentService osztályban. Mi a legelőször javasolt lépés?

a) Azonnal írjunk teszteket, amíg el nem érjük a 80%-ot
b) Nézzük meg a coverage riportot, hogy pontosan melyik ágak és metódusok nincsenek lefedve, és mérlegeljük azok kockázatát
c) Töröljük a meglévő teszteket és írjuk újra őket hatékonyabban
d) Kérjük meg a tech leadet, hogy csökkentse a coverage-elvárást erre az osztályra

Helyes válasz: b)
Először értsük meg, mi nincs lefedve és miért. A PaymentService kritikus – ha a hiányzó 55% a fizetési hibakezelést tartalmazza, az sürgős. Ha csak konfiguráció-getter metódusok, az kevésbé sürgős.


6. Melyik kódrészletnek van magasabb branch coverage-e ugyanennyi line coverage mellett?

A változat:

def process(x):
    return x * 2

B változat:

def process(x):
    if x > 0:
        return x * 2
    return 0

a) Az A változatnak, mert kevesebb benne az elágazás
b) A B változatnak, ha mindkét ágát teszteljük
c) Ugyanolyan, ha mindkét fájl 100% line coverage-szel rendelkezik
d) Nem meghatározható coverage riport nélkül

Helyes válasz: b)
A B változatban van egy if x > 0 elágazás. Ha mindkét ágát (x > 0 és x ≤ 0) teszteljük, a branch coverage 100%. Ha csak az egyik esetet teszteljük, a branch coverage 50% – annak ellenére, hogy a line coverage 100% lehet (mert az if sora mindenképpen le fut).


7. Egy senior fejlesztő azt mondja: „Nálunk elvárás a 80% coverage – ha ez megvan, a CI zöld, mehetünk." Mi a probléma ezzel a megközelítéssel?

a) A 80% túl alacsony, legalább 95% kellene
b) A coverage-szám nem mond semmit a tesztek minőségéről – 80%-os, de értéktelen tesztek esetén is zöld lesz a CI
c) A CI-ban nem kellene coverage-ellenőrzést futtatni
d) Nincs probléma, ez egy jó megközelítés

Helyes válasz: b)
A coverage-elvárás hasznos minimumkövetelmény, de nem garantál semmit a tesztek minőségéről. Üresen assertáló tesztek (pl. assert result is not None) vagy triviális getter tesztek felfújhatják a számot anélkül, hogy valódi védelmet adnának.


8. Melyik rétegnél várható általánosan a legmagasabb coverage?

a) Infrastrukturális / konfigurációs kód
b) Controller / route handler réteg
c) Business logic / service réteg
d) Adatbázis migrációs fájlok

Helyes válasz: c)
A business logic tartalmazza az üzleti szabályokat, számításokat, döntési logikát – ezek a legértékesebb és legtesztelendőbb részek. Az infrastruktúra és a migrációk tesztelése sokkal nehezebb és ritkábban szükséges.


9. Rendelkezel egy coverage riporttal, ahol a UserService.deactivateAccount() metódus 0%-os branch coverage-szel rendelkezik. Mit teszel?

a) Azonnal tesztet írsz, mert 0% soha nem elfogadható
b) Megnézed a metódus logikáját, felmériod a kockázatot, és döntesz: kritikus-e elegendő mértékben ahhoz, hogy tesztet írj
c) Jelzed a tech leadnek, hogy ez nem a te feladatod
d) Törlöd a metódust, ha nincs teszt hozzá, valószínűleg nem is használják

Helyes válasz: b)
A 0% riasztó, de a kontextus számít. Ha a deactivateAccount éles, kritikus funkció (pl. fizetős felhasználók adatainak kezelése), mindenképpen teszt kell. Ha egy belső admin eszköz manuálisan futtatott scriptje, a prioritás alacsonyabb lehet.


10. Mikor jelent a 90%-os coverage valódi problémát?

a) Ha a hiányzó 10% tartalmazza az összes kritikus hibakezelési ágat
b) Ha a csapat elvárása 95%
c) Ha a build idő miatt lassabb a CI
d) Ha a codebase több mint 10 000 sort tartalmaz

Helyes válasz: a)
A coverage önmagában nem értékes szám. Ha a 90%-os coverage lefedi az összes triviális kódot, de kimaradnak a hibakezelési útvonalak, a fizetési logika, vagy a biztonsági gate-ek – az a 10% hiány sokkal fontosabb, mint amilyennek látszik.

Scroll to Top