Poglavlje 6: Tehnike bijele kutije

Priča za početak. 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.

Slika 6.1 – goto fail: ponovljeni red preskače provjeru potpisa; sivi redovi se nikad ne izvrše

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.

Literatura. Osnovna literatura predmeta:

  1. J. Popović: Priručnik za test inženjere, CET i Računarski fakultet, Beograd, 2023.
  2. Rakić-Skoković: Priručnik za testiranje softvera, Fakultet tehničkih nauka, Novi Sad, 2013.
  3. G. J. Myers: The Art of Software Testing, 2. izdanje, John Wiley & Sons, 2004.
  4. A. P. Mathur: Foundations of Software Testing, Addison-Wesley Professional, 2008.

Za ovo poglavlje: T. J. McCabe: „A Complexity Measure”, IEEE Transactions on Software Engineering, 1976; P. Ammann, J. Offutt: Introduction to Software Testing, 2nd ed., Cambridge University Press, 2016; dokumentacija alata coverage.py (coverage.readthedocs.io).

Riječi ovog poglavlja.

  • Bijela kutija (white-box, structural testing) – testovi koji se prave i ocjenjuju gledajući kod iznutra.
  • Graf toka kontrole (control flow graph, CFG) – crtež programa: čvorovi su dijelovi koda, strelice su mogući skokovi s jednog na drugi.
  • Odluka (decision) – mjesto gdje program bira put, npr. if; ima ishod DA ili NE.
  • Uslov (condition) – najmanji logički dio odluke; odluka km < 0 or km > 10 ima dva uslova.
  • Ciklomatska složenost V(G) – broj nezavisnih puteva kroz funkciju; računa se kao broj odluka + 1.
  • Bazni put (basis path) – jedan od V(G) puteva od kojih se mogu „sklopiti” svi ostali.
  • Pokrivenost (coverage) – procenat dijelova koda (redova, grana, uslova) koje su testovi izvršili.
  • Pokrivenost iskaza (statement coverage) – koliko je redova koda izvršeno bar jednom.
  • Pokrivenost grana (branch coverage) – koliko je ishoda odluka (DA i NE) izvršeno.
  • MC/DC (Modified Condition/Decision Coverage) – svaki uslov mora bar jednom sam, bez pomoći drugih, promijeniti ishod odluke.
  • Nedostižan kod (dead code, unreachable code) – kod do kojeg program nikad ne može doći.
  • coverage.py – alat za mjerenje pokrivenosti Python koda.

Ciljevi poglavlja. 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.

Plan poglavlja – Sedmica 6: tri časa teorije i tri časa praktične nastave

Č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 %
Praksa 1–3 Mjerenje pokrivenosti DeliveryApp; dopisivanje testova; HTML izvještaj primjeri/pokrivenost/

Dio 1 – Kod kao mapa

6.1 Bijela i crna kutija

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.

Ukratko.

  • Crna kutija pravi testove iz zahtjeva, a bijela kutija pravi i ocjenjuje testove gledajući kod.
  • Bijela kutija pokazuje koji dijelovi koda nisu izvršeni ni u jednom testu.
  • Bijela kutija ne može naći pravilo koje uopšte nije programirano, jer ga nema u kodu.
  • Dvije kutije se dopunjuju: testovi iz pravila, provjereni mjerenjem pokrivenosti.

6.2 Graf toka kontrole

Da bismo brojali puteve kroz kod, prvo ga crtamo. Graf toka kontrole ima:

  • čvorove (kružiće): komad koda koji se izvršava bez skretanja, ili jednu odluku;
  • grane (strelice): moguće skokove s jednog komada na drugi;
  • jedan ulaz i jedan izlaz (kraj funkcije, bilo da je 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 13

Crtamo ga korak po korak (slika 6.2):

  1. Čvor 1 je prva odluka (red 6). Ako je DA, ide se u čvor 2 (raise, red 7) i funkcija završava. Ako je NE, ide se dalje.
  2. Čvor 3 je druga odluka (red 8). DA vodi u čvor 4 (raise, red 9), NE vodi dalje.
  3. Čvor 5 je treća odluka (red 10). DA vodi u čvor 6 (return 0), NE u čvor 7.
  4. Čvor 7 su redovi 12 i 13 zajedno: izvršavaju se uvijek jedan za drugim, bez skretanja, pa su jedan čvor.
  5. Svi završeci (2, 4, 6, 7) vode u zajednički čvor kraj.

Slika 6.2 – Graf toka kontrole funkcije cijena_dostave: tri odluke (narandžasto), četiri završetka

Brojimo: 8 čvorova (1–7 i kraj) i 10 grana (šest izlaza iz odluka i četiri strelice prema kraju).

Na času. 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.

Ukratko.

  • Graf toka kontrole crta program kao čvorove (komade koda i odluke) i grane (moguće skokove).
  • Redovi koji se uvijek izvršavaju jedan za drugim čine jedan čvor.
  • Svaka odluka if ima dvije izlazne grane, DA i NE.
  • Graf funkcije cijena_dostave ima 8 čvorova i 10 grana.

6.3 Ciklomatska složenost i bazni putevi

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:

\[V(G) = E - N + 2\]

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.

Česta zabluda. „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)?

  1. Najmanji broj testova za puno obilaženje grana. Za V(G) = 4 trebaju nam bar četiri testa (osim kad jedan test slučajno prođe dva puta, što nije moguće u funkciji s raise).
  2. Upozorenje na složen kod. McCabe je predložio da funkcija ne prelazi 10. Funkcija s V(G) = 25 ima 25 nezavisnih puteva; teško je razumjeti, teško testirati i lako je pokvariti. Alat ruff iz poglavlja 7 ima pravilo C901 koje javlja funkcije složenije od zadate granice.
  3. Mjesto za refaktorisanje. Složenu funkciju često je bolje razbiti na nekoliko manjih, svaku s malim V(G), nego pisati 25 testova za jednu.

Broj za pamćenje. V(G) = broj odluka + 1. Preko 10 je znak da funkciju treba podijeliti.

Ukratko.

  • Ciklomatska složenost V(G) broji nezavisne puteve kroz funkciju: V(G) = E − N + 2 = broj odluka + 1.
  • Za cijena_dostave V(G) je 4, ili 5 ako se uslovi u or broje posebno.
  • Četiri bazna puta daju četiri testa koji zajedno izvrše svaku granu grafa.
  • Očekivani rezultat na kraju puta uvijek dolazi iz pravila, a ne iz koda.
  • Funkcija s V(G) većim od 10 teško se testira i obično je treba podijeliti.

Provjeri se.

  1. Šta je čvor, a šta grana u grafu toka kontrole? – Čvor je komad koda bez skretanja ili jedna odluka; grana je mogući skok s jednog čvora na drugi.
  2. Kolika je V(G) funkcije s pet naredbi if i bez petlji? – 5 + 1 = 6.
  3. Koji test prolazi bazni put P3 funkcije 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 €.

Dio 2 – Kriteriji pokrivenosti

6.4 Pokrivenost iskaza

Najjednostavnije pitanje je: koliko redova koda je izvršeno bar jednom?

\[\text{pokrivenost iskaza} = \frac{\text{izvršeni iskazi}}{\text{svi iskazi}} \times 100\,\%\]

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.

Iz stvarnog svijeta. 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.

Ukratko.

  • Pokrivenost iskaza je procenat naredbi koje su testovi izvršili bar jednom.
  • Test N-1001 sam izvrši 7 od 10 iskaza funkcije cijena_dostave, a s N-1002 to je 8 od 10.
  • Redovi koji se nikad ne izvrše vide se odmah, kao provjera potpisa u goto fail.
  • Pokrivenost iskaza je najslabiji kriterij, ali je bolji nego nikakav.

6.5 Pokrivenost grana (odluka)

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 cijena

Jedan 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.

\[\text{pokrivenost grana} = \frac{\text{izvršeni ishodi odluka}}{2 \times \text{broj odluka}} \times 100\,\%\]

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.

Za radoznale. 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 %.

Ukratko.

  • Pokrivenost grana traži da svaka odluka bar jednom da DA i bar jednom NE.
  • if bez else može imati 100 % pokrivenost iskaza, a da grana NE nikad nije izvršena.
  • Pokrivenost grana uključuje pokrivenost iskaza, ali ne i obrnuto.
  • Funkcija cijena_dostave ima 6 ishoda odluka; N-1001 i N-1002 zajedno pokriju 4.

6.6 Pokrivenost uslova i MC/DC

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.

Za radoznale. 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:

  1. svaka odluka bar jednom DA i bar jednom NE (kao grane);
  2. svaki uslov bar jednom tačan i bar jednom lažan (kao uslovi);
  3. svaki uslov mora bar jednom sam promijeniti ishod odluke, dok ostali uslovi stoje isto.

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
  • Par T1 i T2 pokazuje uslov A: B stoji (ne), A se promijeni s ne na da, i odluka se promijeni s NE na DA.
  • Par T1 i T3 pokazuje uslov B: A stoji (ne), B se promijeni, i odluka se promijeni.

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?”

Česta zabluda. „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.

Ukratko.

  • Uslov je najmanji logički dio odluke; odluka km < 0 or km > 10 ima dva uslova.
  • Pokrivenost grana ne traži da svaki uslov bude tačan, a pokrivenost uslova ne traži oba ishoda odluke.
  • MC/DC traži da svaki uslov bar jednom sam promijeni ishod odluke, što dokazuje da je potreban.
  • Za odluku s n uslova MC/DC obično treba n + 1 testova, a sve kombinacije 2ⁿ.
  • Avionski standard DO-178C traži MC/DC za najkritičniji softver.

6.7 Pokrivenost putanja i hijerarhija kriterija

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.

Slika 6.3 – Hijerarhija kriterija pokrivenosti: iskazi ⊂ grane ⊂ MC/DC ⊂ višestruki uslovi; putanje uključuju grane
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.

Na času. 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.

Ukratko.

  • Pokrivenost putanja traži svaki mogući put, ali petlje čine broj puteva ogromnim ili beskonačnim.
  • U praksi se koriste bazni putevi, a petlje se testiraju s 0, 1, 2 i mnogo prolaza.
  • Kriteriji se poredaju po snazi: iskazi, grane, MC/DC, višestruki uslovi.
  • Jači kriterij traži više testova, ali daje veće povjerenje.

Provjeri se.

  1. Može li funkcija imati 100 % pokrivenost iskaza i 50 % grana? – Da: if bez else testiran samo s ishodom DA.
  2. Koliko testova treba za MC/DC odluke A or B? – Tri: oba lažna, samo A tačan, samo B tačan.
  3. Zašto se pokrivenost putanja rijetko traži u praksi? – Jer petlje čine broj putanja ogromnim ili beskonačnim.

Dio 3 – Mjerimo: coverage.py

6.8 Prvo mjerenje

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 -m

Prva 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 -m
Name         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 html

Otvorite 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.

Za radoznale. 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.

Ukratko.

  • coverage.py pokreće testove naredbom coverage run -m pytest i bilježi izvršene redove.
  • coverage report -m ispisuje procenat i brojeve redova koji nisu izvršeni.
  • Opcija --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.

6.9 Od 62 % do 100 %

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 -m
Name         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.

Slika 6.4 – Pokrivenost iskaza i grana dok se dodaju testovi: 1, 2 i 5 testova

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.

Na času. 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.

Ukratko.

  • Svaki broj u koloni Missing je zadatak: test koji treba dopisati.
  • Za cijena_dostave pokrivenost grana raste s 62 % (1 test) na 75 % (2 testa) i 100 % (5 testova).
  • Test s 11 km ne povećava pokrivenost grana, ali ga traži MC/DC.
  • Testovi DeliveryApp iz poglavlja 4 i 5 pokrivaju 98 % fajla deliveryapp.py.

6.10 Zašto 100 % nije kraj

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 0

Sada 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.01s
Name         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) == 300
FAILED test_granica.py::test_tacno_30_eura_besplatno - assert 300 == 0
1 failed, 6 passed in 0.04s

Za 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:

  1. Pokrivenost pokazuje šta NIJE testirano, i tu je vrlo korisna. Crveni red je sigurno netestiran.
  2. Pokrivenost ne pokazuje da je nešto DOBRO testirano. Zeleni red je izvršen, ali možda bez provjere (test bez assert također podiže pokrivenost!) ili s pogrešnim podacima.
  3. Kad pokrivenost postane cilj, prestaje biti dobra mjera. Tim kojem šef kaže „mora biti 100 %” brzo nauči pisati testove koji izvršavaju kod, ali ništa ne provjeravaju.

Zato se u praksi postavlja razuman prag, a ne 100 %. coverage.py ga može provjeriti sam:

coverage report --fail-under=80

Ako 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.

Česta zabluda. „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.

Ukratko.

  • Pokrivenost mjeri da li je kod izvršen, a ne da li je rezultat provjeren pravom vrijednošću.
  • Greška D-01 (> umjesto >=) preživi 100 % pokrivenosti, a uhvati je test s tačno 30,00 €.
  • Pokrivenost je odlična za nalaženje netestiranog koda, a slaba kao dokaz kvaliteta testova.
  • U praksi se postavlja razuman prag, npr. --fail-under=80, a ne slijepa trka za 100 %.

Provjeri se.

  1. Šta znači kolona Missing u izvještaju coverage report -m? – Brojevi redova koje nijedan test nije izvršio.
  2. Da li test bez assert povećava pokrivenost? – Da: kod je izvršen, iako ništa nije provjereno.
  3. Koji test hvata D-01 kad 100 % pokrivenosti ne hvata? – Test na granici: tačno 30,00 € mora dati besplatnu dostavu.

Praktična nastava – Mjerimo pokrivenost DeliveryApp

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.

Korak 1 – Graf i bazni putevi rukom (30 minuta)

  1. Nacrtajte na papiru graf toka kontrole funkcije napojnica(iznos, procenat) iz primjeri/deliveryapp/napojnica.py iz poglavlja 5.
  2. Izračunajte V(G) na dva načina (E − N + 2 i broj odluka + 1).
  3. Napišite tabelu baznih puteva: put, ulaz, očekivani rezultat, pravilo (PP-8). Očekivani rezultat računajte iz pravila, ne iz koda.

Korak 2 – Od 62 % do 100 % (45 minuta)

  1. U folderu primjeri/pokrivenost instalirajte coverage (pip install coverage) i pokrenite coverage run --branch -m pytest test_dva.py i coverage report -m. Zapišite brojeve.
  2. Napravite svoj fajl test_moji.py koji počinje samo s testom N-1001. Izmjerite (očekivano 62 %).
  3. Dodajte po jedan test, svaki put izmjerite i zapišite tabelu: test, pokrivenost iskaza, pokrivenost grana, redovi u Missing.
  4. Otvorite coverage html i napravite snimak ekrana prije i poslije (crveni, žuti i zeleni redovi).

Korak 3 – Pokrivenost cijelog DeliveryApp (30 minuta)

  1. U folderu primjeri/deliveryapp pokrenite coverage run --branch -m pytest tests i coverage report -m --include="deliveryapp.py,napojnica.py".
  2. Koji red nije pokriven? Dopišite test za funkciju eur (npr. 2450 → „24,50 €”, 5 → „0,05 €”) i dovedite deliveryapp.py do 100 %.
  3. Pokrenite isto mjerenje sa 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”.

Korak 4 – Mutant koji preživi 100 % (30 minuta)

  1. Dok test_svi.py daje 100 %, u dostava.py promijenite >= u >. Pokrenite testove: šta se desi s pokrivenošću i s prolazima?
  2. Napravite još tri male izmjene, jednu po jednu: 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)?)
  3. Za svaku izmjenu koja preživi napišite test koji je hvata (kao test_granica.py). Na kraju vratite originalni kod i provjerite da svi testovi prolaze.

Korak 5 – Prag pokrivenosti (15 minuta)

  1. Pokrenite coverage report --fail-under=80 sa test_dva.py i sa test_svi.py. Zapišite poruke i kod izlaza (u PowerShellu $LASTEXITCODE).
  2. Raspravite u paru: koji prag biste postavili za DeliveryApp i zašto ne 100 %?

Korak 6 – AI i pokrivenost (opcionalno)

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.

Vježbe

Vježba za studenta 1 – graf i V(G).

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

Rješenje. Č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).

Vježba za studenta 2 – iskazi i grane.

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?

Rješenje. 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 €.

Vježba za studenta 3 – MC/DC.

Odluka glasi if iznos >= 3000 and kod_ok: (besplatna dostava samo s važećim kodom). Napravite najmanji skup testova za MC/DC.

Rješenje. 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”.

Vježba za studenta 4 – čitanje izvještaja.

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?

Rješenje. 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.

Vježba za studenta 5 – 100 % i greška.

Kolega kaže: „Funkcija popust ima 100 % pokrivenosti grana, ne treba joj više testova.” Navedite grešku koja bi mogla proći.

Rješenje. 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.

Sažetak u pet rečenica

  1. Greška goto fail iz 2014. bila je jedan ponovljen red koji je preskakao provjeru potpisa, a mjerenje pokrivenosti bi pokazalo da se ti redovi nikad ne izvrše.
  2. Bijela kutija pravi i ocjenjuje testove gledajući kod, crta ga kao graf toka kontrole i broji nezavisne puteve ciklomatskom složenošću V(G) = broj odluka + 1, što je za cijena_dostave 4.
  3. Kriteriji pokrivenosti idu od slabijeg ka jačem: iskazi, grane, MC/DC i višestruki uslovi, a pokrivenost putanja je zbog petlji uglavnom teorijski ideal.
  4. Alat coverage.py mjeri pokrivenost naredbama coverage run --branch -m pytest i coverage report -m, a kolona Missing je spisak testova koje treba dopisati, od 62 % do 100 %.
  5. Pokrivenost pokazuje šta nije testirano, ali ne i da je testirano dobro: greška D-01 preživljava 100 % pokrivenosti, a hvata je tek test na granici od 30,00 €.

Pitanja za ponavljanje

  1. Šta je bila greška goto fail i kako bi je otkrilo mjerenje pokrivenosti?
  2. Koja je razlika između testiranja crnom i bijelom kutijom? Šta bijela kutija ne može naći?
  3. Šta su čvorovi i grane u grafu toka kontrole? Koliko ih ima graf funkcije cijena_dostave?
  4. Navedite dva načina računanja ciklomatske složenosti i izračunajte je za cijena_dostave.
  5. Šta su bazni putevi? Navedite bazne puteve funkcije cijena_dostave s ulazima i očekivanim rezultatima.
  6. Zašto je funkcija s V(G) većom od 10 problem?
  7. Šta je pokrivenost iskaza i kolika je za test N-1001?
  8. Pokažite primjerom da 100 % pokrivenosti iskaza ne znači 100 % pokrivenosti grana.
  9. Koja je razlika između odluke i uslova?
  10. Šta traži MC/DC? Napravite testove za odluku km < 0 or km > 10.
  11. Zašto se pokrivenost putanja rijetko koristi u praksi?
  12. Kojim naredbama se mjeri pokrivenost grana u coverage.py i šta znače kolone Branch, BrPart i Missing?
  13. Zašto 100 % pokrivenosti ne garantuje kod bez grešaka? Navedite primjer iz DeliveryApp.
  14. Šta radi opcija --fail-under i zašto se prag obično ne postavlja na 100 %?

Odgovori na pitanja za ponavljanje.

  1. U Appleovoj biblioteci za sigurne veze ponovljen red goto fail; izvršavao se uvijek i preskakao provjeru digitalnog potpisa servera, a err je bio 0 („sve u redu”), pa je telefon prihvatao lažne servere; ispravljeno je u iOS 7.0.6, februara 2014. Mjerenje pokrivenosti iskaza bi pokazalo da redovi s provjerom potpisa imaju nula izvršenja.
  2. Crna kutija pravi testove iz zahtjeva i pravila, ne gledajući kod; bijela kutija pravi i ocjenjuje testove gledajući kod (odluke, grane, puteve). Bijela kutija ne može naći pravilo koje uopšte nije programirano, jer ga nema u kodu.
  3. Čvor je komad koda koji se izvršava bez skretanja ili jedna odluka, a grana je mogući skok s jednog čvora na drugi. Graf funkcije cijena_dostave ima 8 čvorova i 10 grana.
  4. V(G) = E − N + 2 = 10 − 8 + 2 = 4, ili V(G) = broj odluka + 1 = 3 + 1 = 4 (5 ako se uslovi u or broje posebno).
  5. Bazni putevi su V(G) nezavisnih puteva od kojih se sklapaju svi ostali. Za cijena_dostave: −1 km → ValueError (PP-3); 5 km i 4,50 € → ValueError (PP-1); 2,5 km i 32,00 € → 0,00 € (PP-4); 4,2 km i 21,50 € → 3,00 € (PP-2).
  6. Ima mnogo nezavisnih puteva, pa je teško razumjeti, treba mnogo testova i lako je pokvariti; McCabe je predložio granicu 10, a takvu funkciju je obično bolje podijeliti na manje.
  7. Procenat naredbi koje su testovi izvršili bar jednom. Za N-1001 sam to je 7 od 10 iskaza, dakle 70 % (nisu izvršeni oba raise i return 0).
  8. Funkcija dostava_s_kodom s if bez else, testirana samo s kodom „BESPLATNO”: svi iskazi su izvršeni (100 %), ali ishod NE odluke nikad nije, pa je pokrivena samo polovina grana.
  9. Odluka je cijeli izraz u if koji bira put (DA ili NE); uslov je najmanji logički dio odluke. Odluka km < 0 or km > 10 ima dva uslova.
  10. MC/DC traži da svaka odluka i svaki uslov budu i tačni i lažni, i da svaki uslov bar jednom sam promijeni ishod odluke. Za km < 0 or km > 10: 5 km (ne, ne → NE), −1 km (da, ne → DA), 11 km (ne, da → DA).
  11. Jer petlje čine broj putanja ogromnim ili beskonačnim (petlja s jednim if kroz 20 stavki daje više od milion putanja), pa se umjesto toga koriste bazni putevi i petlje se testiraju s 0, 1, 2 i mnogo prolaza.
  12. coverage run --branch -m pytest pa coverage report -m. Branch je broj ishoda odluka, BrPart broj odluka kod kojih je izvršen samo jedan ishod, a Missing brojevi redova koji nisu izvršeni.
  13. Jer pokrivenost mjeri da li je kod izvršen, a ne da li je rezultat provjeren pravom vrijednošću. Greška D-01 (iznos > 3000 umjesto >=) prolazi kroz pet testova sa 100 % pokrivenosti, jer nijedan test nema tačno 30,00 €; hvata je test na granici.
  14. --fail-under=80 završava naredbu greškom ako je pokrivenost ispod 80 %, pa ga CI može koristiti da zaustavi izmjenu. Prag se ne postavlja na 100 % jer tada pokrivenost postaje cilj i tim piše testove koji izvršavaju kod a ništa ne provjeravaju; dio koda (npr. ispis) rijetko vrijedi takvog truda.