Dio 1 – Kod kao mapa
Dio 2 – Kriteriji pokrivenosti
Dio 3 – Mjerimo: coverage.py
Praksa – Mjerimo pokrivenost DeliveryApp
Materijale priredio Erkan Islamović
erkan.islamovic@eubd.edu.ba · erkan.islamovic@gmail.com
Petnaest sedmica: svaka ima tri časa teorije i tri časa praktične nastave. Danas: poglavlje 6 (Sedmica 6, označeno). Isti primjer kroz knjigu: DeliveryApp, narudžba N-1001.
| Sed. | Poglavlje | Teorija (3 časa) | Praksa (3 časa) |
|---|---|---|---|
| 1 | Zašto testiramo | Uvodne priče; greška, defekt, otkaz; sedam principa; DeliveryApp | Tabela očekivanih cijena rukom |
| 2 | Proces i nivoi | Test proces; nivoi i V-model; vrste; statičko testiranje | Recenzija zahtjeva DeliveryApp |
| 3 | Manuelno testiranje | Test slučaj; izvještaj o defektu; istraživačko testiranje | Test slučajevi za web stranicu |
| 4 | Automatizovano testiranje | Šta automatizovati; piramida testova; prvi test | Prvi pytest |
| 5 | Jedinično testiranje | AAA; fixture, parametri, izuzeci; TDD | Testovi za dostavu i popust |
| 6 | Bijela kutija | Graf toka; pokrivenost; ciklomatska složenost | coverage.py do 100 % grana |
| 7 | Tok podataka | Definicija i upotreba; anomalije; statička analiza, mutacije | ruff, mypy, ručna mutacija |
| 8 | Crna kutija | Ekvivalencija i granice; tabela odlučivanja; stanja | Lov na pet grešaka |
| Sed. | Poglavlje | Teorija (3 časa) | Praksa (3 časa) |
|---|---|---|---|
| 9 | Model i integracija | Model; strategije integracije; API | TestClient za API |
| 10 | Sistemsko testiranje | Sistemsko; performanse i sigurnost; prihvatno i BDD | Locust, Gherkin |
| 11 | OO testiranje | Stanje objekta; nasljeđivanje; dvojnici | Narudzba, unittest.mock |
| 12 | Paralelne aplikacije | Trka podataka; zastoj; nestabilni testovi | Niti, 1000 ponavljanja |
| 13 | Web aplikacije | Web i DOM; Playwright; Page Object, pristupačnost | Playwright za DeliveryApp |
| 14 | Upravljanje testiranjem | Test plan i rizik; metrike; CI/CD | GitHub Actions |
| 15 | Alati, AI, projekat | Pregled alata; AI u testiranju; ISTQB | Odbrana projekta |
U petak, 21. februara 2014., Apple je izdao malu, neobičnu ispravku za iPhone i iPad: iOS 7.0.6. Opis je imao jednu rečenicu: napadač „u privilegovanom položaju na mreži” mogao je presresti i promijeniti podatke koji su trebali biti zaštićeni. Programeri širom svijeta odmah su otvorili javno objavljen izvorni kod Appleove biblioteke za sigurne veze (SSL/TLS) i za nekoliko sati našli uzrok. U funkciji koja provjerava digitalni potpis servera stajala su, jedan ispod drugog, dva ista reda: goto fail;. Prvi je bio ispravan i pripadao je naredbi if. Drugi je bio višak, vjerovatno nastao pri spajanju izmjena, i izvršavao se uvijek.
Program je zato skakao na kraj funkcije prije nego što bi uopšte provjerio potpis, a promjenljiva err je u tom trenutku imala vrijednost 0, što znači „sve u redu”. Telefon je tako prihvatao lažni server kao pravi: napadač na istoj Wi-Fi mreži u kafiću mogao je glumiti banku ili poštu. Ranjivost je dobila oznaku CVE-2014-1266 i nadimak „goto fail”, a ispravka za Mac računare (OS X 10.9.2) stigla je nekoliko dana kasnije. Najzanimljivije za ovo poglavlje je jedno pitanje koje su tada postavili mnogi: kako niko nije primijetio da se provjera potpisa nikad ne izvrši? Alat koji broji koji su redovi koda izvršeni u testovima pokazao bi da redovi za provjeru potpisa nisu izvršeni nijednom. Taj alat se zove mjerenje pokrivenosti koda, i to je glavna tema ovog poglavlja.

U poglavljima 3 do 5 test smo pravili iz pravila: PP-2 kaže koliko košta dostava, pa test provjeri tu cijenu. Nismo gledali unutra, u kod. Tako se radi crnom kutijom i detaljno ćemo je učiti u poglavlju 8. Ovo poglavlje otvara poklopac. Kod cijena_dostave koji smo testirali u poglavljima 4 i 5 sada crtamo kao graf, brojimo njegove puteve i mjerimo koliko ga naši testovi stvarno obilaze. Prvo učimo graf toka kontrole i ciklomatsku složenost. Zatim kriterije pokrivenosti: iskaze, grane, uslove i MC/DC. Na kraju mjerimo pokrivenost alatom coverage.py i vidimo njegovu najvažniju pouku: 100 % pokrivenosti ne znači da grešaka nema.
if; ima ishod DA ili NE.km < 0 or km > 10 ima dva uslova.Nakon poglavlja možete:
objasniti razliku između bijele i crne kutije; nacrtati graf toka kontrole za kratku funkciju; izračunati ciklomatsku složenost na dva načina i napisati bazne puteve; razlikovati pokrivenost iskaza, grana, uslova, MC/DC i putanja i reći koja uključuje koju; za odluku s dva uslova napraviti najmanji skup testova za MC/DC; izmjeriti pokrivenost alatom coverage.py i pročitati kolonu „Missing”; dopisati testove tako da pokrivenost grana poraste do 100 %; objasniti zašto 100 % pokrivenosti ne garantuje kod bez grešaka.
| Čas | Sadržaj | Na času |
|---|---|---|
| Teorija 1 | 6.1–6.3 Bijela kutija; graf toka kontrole; ciklomatska složenost i bazni putevi | Graf funkcije cijena_dostave na tabli |
| Teorija 2 | 6.4–6.7 Pokrivenost iskaza, grana, uslova, MC/DC i putanja | Koliko testova treba za MC/DC? |
| Teorija 3 | 6.8–6.10 coverage.py; od 62 % do 100 %; zašto 100 % nije dovoljno | Greška D-01 koja preživi 100 % |
Zamislite da testirate auto. Možete sjesti, voziti i provjeriti da li koči, da li skreće i koliko troši. Ne znate šta je ispod haube, ali znate šta auto treba da radi. To je crna kutija: test se pravi iz zahtjeva, a unutrašnjost se ne gleda. Druga mogućnost je da otvorite haubu, kao mehaničar, i provjerite svaku cijev i svaki kabl: da li je svaki dio bar jednom proradio. To je bijela kutija (ponekad i „staklena kutija”): test se pravi i ocjenjuje gledajući kod.
Dvije kutije odgovaraju na različita pitanja:
| Crna kutija | Bijela kutija | |
|---|---|---|
| Izvor testova | zahtjevi i pravila (PP-1 … PP-6) | kod: odluke, grane, putevi |
| Pitanje | Radi li program ono što treba? | Jesu li testovi obišli sav kod? |
| Hvata | pogrešno ponašanje, zaboravljen slučaj iz pravila | kod koji nijedan test ne izvršava, mrtav kod, zaboravljenu granu |
| Ne hvata | kod koji radi nešto što pravilo ne spominje | pravilo koje kod uopšte nije implementirao |
| Ko najčešće radi | tester, korisnik, analitičar | programer, uz jedinične testove |
Posljednji red tabele je važan. Bijela kutija ne može naći ono čega u kodu nema. Ako je programer zaboravio pravilo PP-4 (besplatna dostava), u kodu nema odluke iznos >= 3000, pa je nema ni u grafu, i bijela kutija ne vidi da nešto fali. Zato se dvije kutije dopunjuju: crnom kutijom napravimo testove iz pravila, a bijelom provjerimo da li su ti testovi obišli sav kod.
Slika iz života: pokrivenost je kao obilazak grada. Poštar koji kaže „obišao sam grad” može misliti na glavnu ulicu. Mapa na kojoj su obojene ulice kojima je zaista prošao odmah pokazuje koje ulice nije vidio. Graf toka kontrole je mapa, a alat za pokrivenost boji ulice.
Da bismo brojali puteve kroz kod, prvo ga crtamo. Graf toka kontrole ima:
return ili raise).Naša funkcija iz primjeri/pokrivenost/dostava.py je ista kao u DeliveryApp, samo s brojevima umjesto konstanti, da se lakše čita:
def cijena_dostave(udaljenost_km, iznos):
if udaljenost_km < 0 or udaljenost_km > 10: # red 6
raise ValueError("udaljenost izvan zone dostave") # red 7
if iznos < 500: # red 8
raise ValueError("iznos manji od minimalne narudžbe") # red 9
if iznos >= 3000: # red 10
return 0 # red 11
dodatni_km = math.ceil(max(0, udaljenost_km - 3)) # red 12
return 200 + 50 * dodatni_km # red 13Crtamo ga korak po korak (slika 6.2):
raise, red 7) i funkcija završava. Ako je NE, ide se dalje.raise, red 9), NE vodi dalje.return 0), NE u čvor 7.
Brojimo: 8 čvorova (1–7 i kraj) i 10 grana (šest izlaza iz odluka i četiri strelice prema kraju).
Nacrtajte graf za funkciju popust(iznos, kod) iz deliveryapp.py: if not kod: return 0, pa procenat = …, pa if procenat is None: return 0, pa return min(…). Koliko čvorova i grana ima?
Odgovor: ulaz s prvom odlukom (1), return 0 (2), red s procenat i druga odluka (3), return 0 (4), return min (5) i kraj (6): 6 čvorova; grane 1→2, 1→3, 3→4, 3→5, 2→kraj, 4→kraj, 5→kraj: 7 grana.
if ima dvije izlazne grane, DA i NE.cijena_dostave ima 8 čvorova i 10 grana.Koliko testova treba da bismo „prošli” svaki dio grafa? Odgovor daje ciklomatska složenost, mjera koju je Thomas McCabe predložio 1976. godine. Ona broji nezavisne puteve kroz funkciju. Postoje tri načina da se izračuna, i svi daju isti broj:
gdje je E broj grana, a N broj čvorova. Za cijena_dostave: V(G) = 10 − 8 + 2 = 4.
Drugi način je lakši za glavu: V(G) = broj odluka + 1. Funkcija ima tri if, pa je V(G) = 3 + 1 = 4.
Treći način je za one koji vole crtati: broj zatvorenih oblasti u grafu + 1 (oblasti koje strelice ograđuju kad se graf nacrta bez presijecanja).
Šta broj 4 znači u praksi? Postoje 4 bazna puta, a svaki drugi put kroz funkciju može se „sklopiti” od njih. Ako testovi prođu sva četiri, svaka grana grafa izvršena je bar jednom.
| Put | Čvorovi | Ulaz testa | Očekivano | Pravilo |
|---|---|---|---|---|
| P1 | 1 → 2 → kraj | −1 km, 10,00 € | ValueError „izvan zone” | PP-3 |
| P2 | 1 → 3 → 4 → kraj | 5 km, 4,50 € | ValueError „minimalne” | PP-1 |
| P3 | 1 → 3 → 5 → 6 → kraj | 2,5 km, 32,00 € (N-1002) | 0,00 € | PP-4 |
| P4 | 1 → 3 → 5 → 7 → kraj | 4,2 km, 21,50 € (N-1001) | 3,00 € | PP-2 |
Primijetite da svaki red ima očekivani rezultat iz pravila, a ne iz koda. Graf nam kaže koje puteve treba proći, a pravilo kaže šta treba dobiti na kraju puta.
„Odluka km < 0 or km > 10 je jedan if, dakle jedna odluka.” Za graf s jednim čvorom po if to je tačno i V(G) = 4. Ali or krije dva uslova, i ako graf crtamo tako da svaki uslov ima svoj čvor (kako kompajler zaista radi), dobijamo V(G) = 5. Alati se razlikuju: neki broje or i and, neki ne. Važno je da u timu svi broje isto.
Zašto testeru treba V(G)?
raise).cijena_dostave V(G) je 4, ili 5 ako se uslovi u or broje posebno.if i bez petlji? 5 + 1 = 6.cijena_dostave? Narudžba od 30,00 € ili više u zoni, npr. N-1002: 2,5 km i 32,00 €, očekivano 0,00 €.Odgovori: tipka R ili klik na zamućeno polje.
Najjednostavnije pitanje je: koliko redova koda je izvršeno bar jednom?
Iskaz (statement) je jedna naredba: dodjela, if, return, raise, poziv funkcije. Alat coverage.py u fajlu dostava.py broji 10 iskaza: import math, def, tri if, dva raise, return 0, red s dodatni_km i posljednji return. (Red def i import se „izvrše” kad se modul učita.)
Uzmimo samo jedan test, N-1001: 4,2 km i 21,50 €. Kroz koje redove prolazi? Red 6 (NE), red 8 (NE), red 10 (NE), red 12, red 13. Uz import i def to je 7 od 10 iskaza, dakle 70 %. Nisu izvršeni redovi 7, 9 i 11: oba raise i return 0.
Drugi test, N-1002 (2,5 km, 32,00 €), prolazi i kroz red 11. Sada je izvršeno 8 od 10, dakle 80 %, a nedostaju samo redovi 7 i 9: dvije poruke o grešci nikad nisu provjerene.
Pokrivenost iskaza je ono što bi otkrilo goto fail. U Appleovom kodu redovi s provjerom potpisa poslije duplog goto fail; nisu se izvršavali ni u jednom testu, ni u jednom stvarnom pozivu. Izvještaj o pokrivenosti bi te redove pokazao crveno, s nula izvršenja (slika 6.1). Isti problem mogu pokazati i upozorenja kompajlera za nedostižan kod, ali samo ako su uključena.
Nakon goto fail mnogi su istakli da bi test koji šalje namjerno lažan potpis odmah pokazao da ga telefon prihvata. Takav test je crna kutija (iz pravila: „lažan potpis mora biti odbijen”), a mjerenje pokrivenosti je bijela kutija (iz koda: „ovaj red nikad nije izvršen”). Svaki od ta dva pristupa sam bi uhvatio grešku. Nijedan nije bio primijenjen.
cijena_dostave, a s N-1002 to je 8 od 10.Pokrivenost iskaza ima slabu tačku: if bez else. Pogledajte mali primjer iz primjeri/pokrivenost/besplatno.py:
def dostava_s_kodom(cijena, kod):
if kod == "BESPLATNO":
cijena = 0
return cijenaJedan test, dostava_s_kodom(300, "BESPLATNO") == 0, izvrši sva četiri iskaza: 100 % pokrivenost iskaza. Ali nikad nismo provjerili šta se desi kad kod nije „BESPLATNO”. Da je programer napisao cijena = 0 izvan if (greškom uvukao red manje), ovaj test bi i dalje prolazio, a svi kupci bi dobili besplatnu dostavu.
Zato postoji pokrivenost grana (odluka): svaka odluka mora bar jednom dati DA i bar jednom NE.
Za cijena_dostave imamo 3 odluke, dakle 6 ishoda:
| Odluka | DA | NE |
|---|---|---|
| red 6: izvan zone? | P1 (−1 km) | N-1001, N-1002 |
| red 8: ispod 5,00 €? | P2 (4,50 €) | N-1001, N-1002 |
| red 10: od 30,00 €? | N-1002 | N-1001 |
Sam N-1001 daje 3 od 6 ishoda (sve NE), a s N-1002 je 4 od 6. Tek s testovima −1 km i 4,50 € imamo sve ishode.
Pokrivenost grana uključuje pokrivenost iskaza: ako je svaka grana izvršena, izvršen je i svaki iskaz na njoj. Obrnuto ne važi, kako pokazuje dostava_s_kodom.
coverage.py u režimu --branch ispiše jedan broj koji spaja oba kriterija: (izvršeni iskazi + izvršene grane) / (svi iskazi + sve grane). Za N-1001 sam: (7 + 3) / (10 + 6) = 10 / 16 = 62,5 %, a alat ispiše 62 %. Za N-1001 i N-1002: (8 + 4) / 16 = 75 %.
if bez else može imati 100 % pokrivenost iskaza, a da grana NE nikad nije izvršena.cijena_dostave ima 6 ishoda odluka; N-1001 i N-1002 zajedno pokriju 4.Pogledajmo pažljivije prvu odluku:
if udaljenost_km < 0 or udaljenost_km > 10:Ona ima dva uslova: A = udaljenost_km < 0 i B = udaljenost_km > 10. Pokrivenost grana traži samo da cijela odluka bude jednom DA i jednom NE. To postižu testovi −1 km (DA) i 5 km (NE). Ali uslov B tada nikad nije bio istinit! Da je programer napisao udaljenost_km > 100 umjesto > 10, ova dva testa ne bi primijetila: 11 km bi se dostavljalo.
Pokrivenost uslova traži da svaki uslov bude bar jednom istinit i bar jednom lažan. To je jače, ali ima zamku: testovi −1 km (A tačno) i 11 km (B tačno) zajedno pokrivaju oba uslova u oba ishoda, ali odluka je u oba slučaja DA. Grana NE uopšte nije izvršena.
Python računa or kratko (short-circuit): ako je A tačno, B se uopšte ne računa, jer je odluka već DA. Za −1 km Python nikad ne provjeri -1 > 10. Kad govorimo o vrijednosti uslova B u tom testu, mislimo na vrijednost koju bi imao da je izračunat (lažno).
Rješenje koje se koristi u najstrožim oblastima je MC/DC (Modified Condition/Decision Coverage, izmijenjena pokrivenost uslova i odluka). Pravilo zvuči komplikovano, a ideja je jednostavna:
Treća tačka je srž. Ona dokazuje da svaki uslov zaista nešto radi. Uslov koji nikad sam ne mijenja ishod mogao bi se obrisati, i niko ne bi primijetio.
Za odluku A or B tražimo par testova za A i par za B:
| Test | km | A: km < 0 | B: km > 10 | Odluka | Pravilo |
|---|---|---|---|---|---|
| T1 | 5 | ne | ne | NE (dostavlja se) | PP-3 |
| T2 | −1 | da | ne | DA (odbijeno) | PP-3 |
| T3 | 11 | ne | da | DA (odbijeno) | PP-3 |
Tri testa za dva uslova. Opšte pravilo: za odluku s n uslova MC/DC obično treba n + 1 testova. Puna kombinatorika, koja se zove pokrivenost višestrukih uslova, traži sve kombinacije, 2ⁿ: za 2 uslova to su 4, za 10 uslova 1024 testa. Kod nas je četvrta kombinacija (A i B tačni u isto vrijeme: km < 0 i km > 10) nemoguća, pa je MC/DC ovdje isto što i sve moguće kombinacije.
Gdje se MC/DC koristi? Avionski standard DO-178C traži MC/DC za softver najvišeg nivoa kritičnosti (nivo A), čiji otkaz može srušiti avion. Slične zahtjeve imaju standardi za automobile i željeznicu. U web aplikaciji za hranu MC/DC se rijetko mjeri formalno, ali način razmišljanja je koristan svuda: za svaki uslov u složenom if zapitajte se „koji test pokazuje da je ovaj uslov potreban?”
„MC/DC je samo za avione.” Mjeri se formalno uglavnom tamo, ali greška „pogrešna granica u jednom od dva uslova” (11 km se dostavlja) je obična greška u svakoj aplikaciji, i MC/DC je hvata s tri testa.
km < 0 or km > 10 ima dva uslova.Najjači kriterij je pokrivenost putanja: svaki mogući put od ulaza do izlaza mora biti izvršen. U cijena_dostave postoje samo četiri puta (svaki raise i return završava funkciju), pa su putanje isto što i bazni putevi. To je izuzetak.
Čim funkcija ima petlju, broj putanja eksplodira. Zamislite petlju koja prolazi kroz stavke korpe, a u svakoj stavci ima jedan if (npr. „da li je artikal na akciji”). Za korpu od 1 stavke postoje 2 puta, za 2 stavke 4, za 3 stavke 8, za 20 stavki 2²⁰ = 1 048 576 puteva. Za korpu bez gornje granice puteva je beskonačno mnogo. Pokrivenost putanja je zato teorijski ideal, a u praksi se koriste bazni putevi (V(G) testova) i petlje se testuju s 0, 1, 2 i „mnogo” prolaza.
Kriteriji se mogu poredati po snazi (slika 6.3). Strelica znači „uključuje”: ko ispuni gornji kriterij, ispunio je i donji.

| Kriterij | Šta traži | Testova za cijena_dostave |
|---|---|---|
| Iskazi | svaki red bar jednom | 4 (svaki raise i return je poseban red) |
| Grane | svaka odluka DA i NE | 4 |
| MC/DC | svaki uslov sam mijenja odluku | 5 (4 bazna + 11 km za uslov B) |
| Višestruki uslovi | sve moguće kombinacije uslova | 5 (kombinacija „km < 0 i km > 10” je nemoguća) |
| Putanje | svaki put od ulaza do izlaza | 4 |
Primijetite red za MC/DC: četiri bazna puta testiraju zonu samo sa −1 km, pa uslov B nikad nije tačan. Peti test, 11 km, je upravo onaj koji test_svi.py iz praktične nastave ima.
Koliko putanja ima funkcija koja ima tri if jedan ispod drugog, ali bez return i raise unutar njih (svaki if samo promijeni neku promjenljivu)? Koliki je V(G)? – Putanja je 2 × 2 × 2 = 8, a V(G) = 3 + 1 = 4. Bazni putevi su manje od svih putanja: zato je V(G) praktičan kompromis.
if bez else testiran samo s ishodom DA.A or B? Tri: oba lažna, samo A tačan, samo B tačan.Odgovori: tipka R ili klik na zamućeno polje.
Ručno brojanje je dobro za učenje, a za stvarni projekat treba alat. Za Python je to coverage.py. On pokrene testove i za svaki red zapiše da li je izvršen. Instalacija i pokretanje iz foldera primjeri/pokrivenost:
pip install coverage
coverage run -m pytest test_dva.py
coverage report -mPrva naredba pokreće pytest „pod nadzorom”, druga ispisuje izvještaj (-m znači: pokaži brojeve redova koji nedostaju). Za dva testa iz test_dva.py (N-1001 i N-1002) izvještaj je:
Name Stmts Miss Cover Missing
------------------------------------------
dostava.py 10 2 80% 7, 9
------------------------------------------
TOTAL 10 2 80%Čitamo red po red: Stmts je broj iskaza (10), Miss broj neizvršenih (2), Cover procenat (80 %), a Missing tačno koji redovi nisu izvršeni: 7 i 9, dva raise. To se slaže s našim ručnim računom iz 6.4.
Dodajemo mjerenje grana opcijom --branch:
coverage run --branch -m pytest test_dva.py
coverage report -mName Stmts Miss Branch BrPart Cover Missing
--------------------------------------------------------
dostava.py 10 2 6 2 75% 7, 9
--------------------------------------------------------
TOTAL 10 2 6 2 75%Nove kolone: Branch je broj ishoda odluka (6, kao u tabeli iz 6.5), a BrPart (partial) broj odluka kod kojih je izvršen samo jedan ishod (2: odluke u redovima 6 i 8 nikad nisu dale DA). Ukupno je 75 %.
Za čitljiviji prikaz postoji HTML izvještaj:
coverage htmlOtvorite htmlcov/index.html u pregledniku i kliknite na dostava.py. Svaki red je obojen: zeleno izvršen, crveno nije izvršen, žuto odluka kod koje je izvršen samo jedan ishod. Pređite mišem preko žutog reda i alat napiše koja grana fali, npr. „line 6 didn't jump to line 7”. To je mapa grada s obojenim ulicama iz 6.1.
Mnogi projekti koriste dodatak pytest-cov, koji isto radi jednom naredbom: pytest --cov=. --cov-branch --cov-report=term-missing. Rezultat je isti, jer pytest-cov iznutra koristi coverage.py.
coverage run -m pytest i bilježi izvršene redove.coverage report -m ispisuje procenat i brojeve redova koji nisu izvršeni.--branch dodaje pokrivenost grana; kolona BrPart broji odluke s izvršenim samo jednim ishodom.coverage html pravi izvještaj u kojem su redovi obojeni zeleno, crveno i žuto.Kolona Missing nije ocjena, nego spisak zadataka: svaki broj u njoj je test koji fali. Pratimo kako pokrivenost raste dok dodajemo testove (slika 6.4).
Korak 1 – samo N-1001. Pokrivenost grana je 62 %, a nedostaju redovi 7, 9, 11. Treba nam narudžba sa besplatnom dostavom (red 11), nešto izvan zone (red 7) i nešto ispod minimuma (red 9).
Korak 2 – dodajemo N-1002. Red 11 je izvršen; pokrivenost je 75 %, nedostaju 7 i 9.
Korak 3 – dodajemo tri testa izuzetaka iz test_svi.py:
def test_negativna_udaljenost():
with pytest.raises(ValueError):
cijena_dostave(-1, 1000)
def test_izvan_zone():
with pytest.raises(ValueError):
cijena_dostave(11, 1000)
def test_ispod_minimuma():
with pytest.raises(ValueError):
cijena_dostave(5, 450)coverage run --branch -m pytest test_svi.py
coverage report -mName Stmts Miss Branch BrPart Cover
----------------------------------------------
dostava.py 10 0 6 0 100%
----------------------------------------------
TOTAL 10 0 6 0 100%Pet testova, 100 % iskaza i grana. Kolona Missing je prazna.

Primijetite da je test s 11 km tu zbog MC/DC, a ne zbog grana: grane bi bile 100 % i bez njega, jer −1 km već daje DA u prvoj odluci. Bez tog testa bismo imali 100 % grana, a greška > 100 umjesto > 10 bi prošla neopaženo.
Isto mjerenje nad testovima DeliveryApp (primjeri/deliveryapp, coverage run --branch -m pytest tests) daje 98 % za deliveryapp.py. (U folderu su već i testovi iz poglavlja 8; samo s testovima iz poglavlja 4 i 5 rezultat je isti.) Jedini neizvršen red je funkcija eur, koja samo formatira iznos za ekran: za nju još niko nije napisao test.
U HTML izvještaju jedna linija je žuta: if iznos >= 3000: s porukom „line 10 didn't jump to line 11”. Kakav test treba dopisati? – Test u kojem je iznos 30,00 € ili više, npr. N-1002, očekivano 0,00 € dostave.
cijena_dostave pokrivenost grana raste s 62 % (1 test) na 75 % (2 testa) i 100 % (5 testova).deliveryapp.py.Imamo 100 % iskaza i grana. Da li to znači da je cijena_dostave bez grešaka? Napravimo eksperiment. U dostava.py promijenite jedan znak, kao u grešci D-01 iz prve verzije DeliveryApp:
if iznos > 3000: # bilo je >= 3000
return 0Sada narudžba od tačno 30,00 € plaća dostavu, što krši PP-4. Pokrenimo isti skup testova:
coverage run --branch -m pytest test_svi.py
..... [100%]
5 passed in 0.01sName Stmts Miss Branch BrPart Cover
----------------------------------------------
dostava.py 10 0 6 0 100%Pet prolaza i 100 % pokrivenosti, a kod je pogrešan. Zašto? Pokrivenost mjeri da li je red izvršen, a ne da li je provjeren pravom vrijednošću. Naš test za besplatnu dostavu koristi 32,00 €, a za 32,00 € su i >= i > tačni. Nijedan test ne stoji na granici.
Grešku hvata test iz test_granica.py, napravljen iz pravila, a ne iz koda:
def test_tacno_30_eura_besplatno():
assert cijena_dostave(5, 3000) == 0
def test_29_99_placa_dostavu():
assert cijena_dostave(5, 2999) == 300FAILED test_granica.py::test_tacno_30_eura_besplatno - assert 300 == 0
1 failed, 6 passed in 0.04sZa tačno 30,00 € pogrešan kod vraća 300 centi (3,00 € za 5 km), a pravilo kaže 0. Ovakve testove pravi analiza graničnih vrijednosti, tehnika crne kutije iz poglavlja 8. A ideja „namjerno pokvari kod i vidi da li testovi to primijete” zove se testiranje mutacijama i učimo je u poglavlju 7.
Tri pouke za pamćenje:
assert također podiže pokrivenost!) ili s pogrešnim podacima.Zato se u praksi postavlja razuman prag, a ne 100 %. coverage.py ga može provjeriti sam:
coverage report --fail-under=80Ako je pokrivenost ispod 80 %, naredba završava greškom (za test_dva.py: „Coverage failure: total of 75 is less than fail-under=80”). U poglavlju 14 tu naredbu stavljamo u GitHub Actions, pa svaki push koji spusti pokrivenost ispod praga dobije crveni znak.
„Imamo 100 % pokrivenosti, dakle nemamo grešaka.” Ne: imamo dokaz da je svaki red izvršen, ne da je svaki red tačan. D-01 je preživio 100 % pokrivenosti.
> umjesto >=) preživi 100 % pokrivenosti, a uhvati je test s tačno 30,00 €.--fail-under=80, a ne slijepa trka za 100 %.coverage report -m? Brojevi redova koje nijedan test nije izvršio.assert povećava pokrivenost? Da: kod je izvršen, iako ništa nije provjereno.Odgovori: tipka R ili klik na zamućeno polje.
Cilj je izmjeriti pokrivenost koda DeliveryApp, pročitati izvještaj, dopisati testove dok pokrivenost grana ne bude 100 % i pokazati da 100 % ne hvata sve greške. Radi se u parovima.
napojnica(iznos, procenat) iz primjeri/deliveryapp/napojnica.py iz poglavlja 5.primjeri/pokrivenost instalirajte coverage (pip install coverage) i pokrenite coverage run --branch -m pytest test_dva.py i coverage report -m. Zapišite brojeve.test_moji.py koji počinje samo s testom N-1001. Izmjerite (očekivano 62 %).coverage html i napravite snimak ekrana prije i poslije (crveni, žuti i zeleni redovi).primjeri/deliveryapp pokrenite coverage run --branch -m pytest tests i coverage report -m --include="deliveryapp.py,napojnica.py".eur (npr. 2450 → „24,50 €”, 5 → „0,05 €”) i dovedite deliveryapp.py do 100 %.DeliveryApp=v0 (u PowerShellu $env:DeliveryApp="v0") i coverage report -m bez opcije --include. Pokrivenost se mjeri i kad testovi padaju: 10 testova pada, a deliveryapp_v0.py je ipak pokriven 98 %. Zapišite šta to govori o odnosu „pokrivenost” i „prolaz”.test_svi.py daje 100 %, u dostava.py promijenite >= u >. Pokrenite testove: šta se desi s pokrivenošću i s prolazima?udaljenost_km > 10 u > 100; 200 + 50 u 200 + 60; math.ceil(max(0, udaljenost_km - 3)) u math.ceil(udaljenost_km - 3) (bez max). Za svaku zapišite: da li test_svi.py pada? (Savjet za treću: koliko je math.ceil(1 - 3)?)test_granica.py). Na kraju vratite originalni kod i provjerite da svi testovi prolaze.coverage report --fail-under=80 sa test_dva.py i sa test_svi.py. Zapišite poruke i kod izlaza (u PowerShellu $LASTEXITCODE).Dajte AI asistentu funkciju cijena_dostave (bez testova) i zatražite „testove za 100 % pokrivenosti grana”. Pokrenite ih sa --branch. Da li su 100 %? Da li bi uhvatili > umjesto >=? Zapišite koliko testova ima očekivani rezultat izračunat iz pravila, a koliko je samo prepisalo ono što kod vrati.
Rezultat koji se predaje: fajl rezultati-6.md s grafom funkcije napojnica (slika ili Mermaid), V(G) i tabelom baznih puteva; tabela rasta pokrivenosti iz koraka 2 sa snimcima HTML izvještaja; test za eur i izvještaj sa 100 %; tabela mutanata iz koraka 4 s testovima koji ih hvataju; odgovor o pragu iz koraka 5.
Rješenja su sakrivena – tipka R ili klik na okvir.
Nacrtajte graf toka kontrole i izračunajte V(G) za funkciju:
def moze_otkazati(stanje):
if stanje == "NOVA":
return True
if stanje == "PRIHVAĆENA":
return True
return FalseČvorovi: odluka 1 (stanje == "NOVA"), return True (2), odluka 3, return True (4), return False (5), kraj (6): 6 čvorova. Grane: 1→2, 1→3, 3→4, 3→5, 2→kraj, 4→kraj, 5→kraj: 7 grana. V(G) = 7 − 6 + 2 = 3 = 2 odluke + 1. Bazni putevi: „NOVA” → True; „PRIHVAĆENA” → True; „U_PRIPREMI” → False (PP-6).
Za funkciju dostava_s_kodom iz 6.5 imamo jedan test: dostava_s_kodom(300, "BESPLATNO") == 0. Kolika je pokrivenost iskaza, a kolika grana? Koji test dodati?
Iskazi: 100 % (sva četiri). Grane: odluka je dala samo DA, pa je pokrivena 1 od 2 grane. coverage.py ispiše 83 % uz napomenu 5->7: grana iz reda 5 direktno u red 7 (ishod NE) nikad nije izvršena. Dodati test dostava_s_kodom(300, "") == 300 (ili s bilo kojim drugim kodom): bez koda cijena ostaje 3,00 €.
Odluka glasi if iznos >= 3000 and kod_ok: (besplatna dostava samo s važećim kodom). Napravite najmanji skup testova za MC/DC.
Za A and B tri testa: (da, da) → DA; (ne, da) → NE (pokazuje A); (da, ne) → NE (pokazuje B). Npr. 32,00 € i važeći kod → besplatno; 29,99 € i važeći kod → plaća; 32,00 € i nevažeći kod → plaća. Primijetite: za and polazni test je „oba tačna”, a za or (6.6) „oba lažna”.
Izvještaj glasi:
Name Stmts Miss Branch BrPart Cover Missing
--------------------------------------------------------
dostava.py 10 3 6 3 62% 7, 9, 11Šta testovi rade, a šta ne? Koja tri testa dopisati?
Izvršena je samo plaćena dostava (sve tri odluke daju NE); to je jedan test kao N-1001. Fale: red 7 (izvan zone, npr. −1 km), red 9 (ispod minimuma, npr. 4,50 €) i red 11 (besplatna dostava, npr. 32,00 €). Uz test 11 km zbog MC/DC dobijamo test_svi.py.
Kolega kaže: „Funkcija popust ima 100 % pokrivenosti grana, ne treba joj više testova.” Navedite grešku koja bi mogla proći.
Npr. MAX_POPUST = 800 umjesto 500 (greška slična D-05): ako nijedan test nema iznos veći od 50,00 €, min uvijek vrati izračunati popust i pokrivenost ostaje 100 %, a greška prolazi. Treba test granice: 80,00 € sa STUDENT10 → 5,00 €. Druga mogućnost: kod malim slovima „student10” (D-03) – grana je ista, ali ulaz drugačiji.
cijena_dostave 4.coverage run --branch -m pytest i coverage report -m, a kolona Missing je spisak testova koje treba dopisati, od 62 % do 100 %.cijena_dostave?cijena_dostave.cijena_dostave s ulazima i očekivanim rezultatima.km < 0 or km > 10.--fail-under i zašto se prag obično ne postavlja na 100 %?