Reguli mici, diferență mare
Testele bune urmează câteva obiceiuri:
1. Nume clare — test_greet_empty_name spune ce s-a stricat fără să deschizi fișierul.
2. Un comportament per test — testele mici arată direct spre problemă.
3. Tabele pentru multe cazuri — un singur test parametrizat în loc de teste copiate.
4. Mesaje cu valoarea obținută și cea așteptată — un eșec trebuie să se explice singur.
5. Teste separate ca să vezi totul, mai multe assert-uri într-un singur test acolo unde continuarea nu are sens.
6. Rulează testele des — după fiecare schimbare mică, nu o singură dată la final.
7. Testele stau lângă cod — același folder, prefixul test_.
Mesaje de eșec care ajută
Un test picat este citit într-un moment de confuzie — mesajul trebuie să spună toată povestea: ce apel, ce s-a returnat, ce se aștepta.
O formă bună este func(input) = got; așteptat want. Conversia !r dintr-un f-string afișează șirurile între ghilimele, așa că un spațiu lipsă sau o virgulă lipsă se vede imediat.
Un mesaj cu toată povestea
greet returnează salutul greșit. Mesajul nostru arată apelul, valoarea obținută și cea așteptată, iar pytest adaugă dedesubt propria comparație a celor două șiruri — eroarea (cuvânt greșit, lipsesc virgula și semnul exclamării) se vede doar din ieșire, fără să deschizi codul.
Ieșire: program Python complet
# greet are o eroare: cuvânt greșit, lipsesc virgula și „!”.
def greet(name):
return "Hi " + name
def test_greet():
got = greet("Ana")
want = "Hello, Ana!"
assert got == want, f"greet('Ana') = {got!r}; așteptat {want!r}"
Un comportament per test
Un test care verifică cinci lucruri are cinci motive să pice — iar numele lui poate descrie doar unul dintre ele. Împărțirea după comportament păstrează fiecare test mic și numele lui corect. Aici contează de două ori, pentru că un assert picat oprește testul său: cinci verificări într-un singur test înseamnă că vezi mereu doar primul eșec.
Aici salutul normal și varianta de rezervă pentru un nume gol sunt două comportamente, deci primesc două teste. Când unul pică, numele lui îți spune deja ce comportament s-a stricat.
Două comportamente, două teste
test_greet_name verifică cazul normal, test_greet_empty varianta de rezervă. Ieșirea detaliată le listează separat — un eșec ar arăta exact spre un singur comportament.
Ieșire: program Python complet
def greet(name):
if name == "":
return "Hello, stranger!"
return "Hello, " + name + "!"
def test_greet_name():
assert greet("Ana") == "Hello, Ana!"
def test_greet_empty():
assert greet("") == "Hello, stranger!"
Cât este testat: acoperirea
Pluginul pytest-cov (instalat cu pip install pytest-cov) măsoară ce parte din cod execută de fapt testele. Rulează pytest --cov=. și după rularea testelor se adaugă un tabel: câte instrucțiuni are fiecare fișier, câte au ratat testele și procentul acoperit.
Numărul este o busolă, nu un scop: o valoare mică arată căi netestate — aici testul parcurge doar ramura mild, așa că instrucțiunile return freezing și hot nu sunt executate niciodată. O acoperire de 100% nu dovedește că acel cod este corect, dar liniile neacoperite sunt sigur neverificate.
Măsoară acoperirea
describe are trei ramuri, testul o parcurge pe una. Raportul numără 8 instrucțiuni, 2 ratate, 75% acoperit — instrucțiunile return freezing și hot sunt cele ratate. Teste pentru ramura rece și pentru cea caldă ar crește numărul.
Este măsurat întregul fișier, inclusiv testul, de aceea numărul nu este mai mic: toate liniile testului au rulat.
Ieșire: program Python complet
def describe(celsius):
if celsius < 0:
return "freezing"
if celsius > 30:
return "hot"
return "mild"
def test_describe_mild():
assert describe(20) == "mild"