Testarea unitară , uneori testarea unitară sau testarea unitară ( de exemplu, testarea unitară ) este un proces de programare care vă permite să verificați corectitudinea modulelor individuale ale codului sursă al programului , seturi de unul sau mai multe module de program, împreună cu datele de control corespunzătoare, proceduri de utilizare și procesare.
Ideea este de a scrie teste pentru fiecare funcție sau metodă non-trivială. Acest lucru vă permite să verificați rapid dacă următoarea modificare a codului a dus la regresie , adică la apariția erorilor în locurile deja testate ale programului și facilitează, de asemenea, detectarea și eliminarea unor astfel de erori. De exemplu, puteți actualiza în orice moment biblioteca utilizată în proiect la versiunea curentă, rulând teste și identificând incompatibilitățile.
Scopul testării unitare este de a izola părțile individuale ale unui program și de a arăta că acele părți funcționează individual.
Acest tip de testare este de obicei făcut de programatori .
Testarea unitară ulterioară le permite programatorilor să refactoreze în timp ce sunt siguri că unitatea încă funcționează corect ( testarea regresiei ). Acest lucru încurajează programatorii să schimbe codul, deoarece este suficient de ușor să verificați dacă codul încă funcționează după modificare.
Testarea unitară ajută la eliminarea îndoielilor cu privire la modulele individuale și poate fi utilizată pentru o abordare de jos în sus a testării: testarea mai întâi a părților individuale ale programului și apoi a programului ca întreg.
Testele unitare pot fi gândite ca un „document viu” pentru clasa testată . Clienții care nu știu să folosească această clasă pot folosi testul unitar ca exemplu.
Deoarece unele clase pot folosi alte clase, testarea unei singure clase se extinde adesea la clasele înrudite. De exemplu, o clasă folosește o bază de date; în timp ce scrie un test, programatorul descoperă că testul trebuie să interacționeze cu baza de date. Aceasta este o eroare deoarece testul nu trebuie să depășească limita clasei. Ca rezultat, dezvoltatorul retrage conexiunea la baza de date și implementează această interfață folosind propriul obiect simulat . Acest lucru are ca rezultat un cod mai puțin coeziv, minimizând dependențele din sistem.
Testarea software-ului este o sarcină combinatorie. De exemplu, fiecare valoare posibilă a unei variabile booleene ar necesita două teste, unul pentru TRUE și unul pentru FALSE. Ca rezultat, fiecare linie de cod sursă va necesita 3-5 linii de cod de testare.
Algoritmi precum Marching cubes sau arborele roșu-negru au un arbore de decizie ramificat și sunt necesare suite de teste uriașe pentru a verifica toate opțiunile: într-una dintre implementările arborelui roșu-negru din GitHub, au fost făcute douăsprezece teste pentru a verifica inserția [1] . În celălalt, ei construiesc automat 10! = 3,6 milioane de permutări și experimentați-le pe toate [2] .
Ca orice tehnologie de testare, testarea unitară nu vă permite să detectați toate erorile de program. Într-adevăr, aceasta rezultă din imposibilitatea practică de a urmări toate căile posibile de execuție a programului, cu excepția celor mai simple cazuri.
De exemplu, în modelarea matematică . Aplicațiile de afaceri funcționează adesea cu seturi finite și numărabile , în timp ce aplicațiile științifice funcționează cu seturi continue . [3] Prin urmare, este dificil de selectat teste pentru fiecare dintre ramurile programului, este dificil de spus dacă rezultatul este corect, dacă se menține acuratețea etc. Și, în multe cazuri, calitatea modelării este determinată „de ochi. ”, iar ultimul rezultat este înregistrat ca „referință”. Dacă se constată o discrepanță, noul rezultat este verificat manual și se stabilește care este mai bun: cel vechi sau cel nou.
Codul care interacționează cu porturile , temporizatoarele , utilizatorul și alte părți „instabile” ale sistemului este extrem de dificil de testat într-un mediu izolat.
Dar asta nu înseamnă că testarea unitară este complet nepotrivită aici: forțează programatorul să treacă de la fișiere și porturi, de exemplu, la fluxuri abstracte . Acest lucru face ca codul să fie mai general (de exemplu, puteți trece de la fișiere la prize de rețea fără probleme ), mai testabil (puteți verifica situația „conexiune pierdută” scriind un flux care, după emiterea de N octeți, va simula un accident; verificați sub parte Windows din funcțiile de conversie a căii
Este practic o parte instabilă a sistemului. În plus, testele unitare sunt de obicei simple, în timp ce testele pentru sistemele multithreaded, dimpotrivă, ar trebui să fie destul de mari.
La efectuarea testelor unitare, fiecare dintre module este testat separat. Aceasta înseamnă că erorile de integrare, erorile la nivel de sistem, funcțiile executate în mai multe module nu vor fi detectate. În plus, această tehnologie este inutilă pentru testele de performanță. Astfel, testarea unitară este mai eficientă atunci când este utilizată în combinație cu alte tehnici de testare.
Culegere de beneficiile testării unitare necesită respectarea strictă a tehnologiei de testare pe tot parcursul procesului de dezvoltare a software-ului. Este necesar să se păstreze nu numai înregistrări ale tuturor testelor efectuate, ci și ale tuturor modificărilor aduse codului sursă în toate modulele. În acest scop, ar trebui utilizat un sistem de control al versiunilor software . Astfel, dacă o versiune ulterioară a software-ului eșuează un test care a fost trecut cu succes înainte, va fi ușor să verificați variațiile codului sursă și să remediați eroarea. De asemenea, trebuie să vă asigurați că testele nereușite sunt urmărite și analizate în orice moment. Ignorarea acestei cerințe va duce la o avalanșă de rezultate ale testelor eșuate.
Cu excepția celor mai simple cazuri, obiectul testat trebuie să interacționeze cu alte obiecte. Acești „colaboratori” - obiecte stub - sunt realizați extrem de simpli: fie extrem de simplificați (memorie în loc de bază de date), fie proiectați pentru un test specific și repetarea mecanică a sesiunii de schimb. Pot apărea probleme la schimbarea protocolului de schimb, caz în care obiectele stub trebuie să îndeplinească noile cerințe de protocol. [patru]
Este ușor să verificați dacă modulul funcționează pe mașina dezvoltatorului. Mai dificil - că pe mașina țintă, adesea foarte limitat [5] .
Programarea extremă presupune ca unul dintre postulate utilizarea instrumentelor automate de testare unitară. Acest set de instrumente poate fi creat fie de o terță parte (cum ar fi Boost.Test), fie de echipa de dezvoltare a aplicației.
Programarea extremă utilizează teste unitare pentru dezvoltarea bazată pe teste . Pentru a face acest lucru, dezvoltatorul, înainte de a scrie codul, scrie un test care reflectă cerințele pentru modul. Evident, testul înainte de scrierea codului nu ar trebui să funcționeze. Procesul ulterior se reduce la scrierea celui mai scurt cod care satisface acest test. După ce dezvoltatorul scrie următorul test, cod și așa mai departe de multe ori.
Complexitatea scrierii testelor unitare depinde de modul în care este organizat codul. O coeziune puternică sau o zonă mare de responsabilitate a entităților individuale (clase pentru limbaje orientate pe obiecte) poate face testarea dificilă. Stub-urile trebuie create pentru obiectele care comunică cu lumea exterioară (rețea, I/O fișier etc.). În terminologie, se disting stub-uri mai „avansate” - obiecte simulate care poartă logică. De asemenea, este mai ușor de testat prin separarea cât mai mult posibil din logică în funcții pure . Ele nu interacționează cu lumea exterioară în niciun fel și rezultatul lor depinde doar de parametrii de intrare.
Se obișnuiește să se separe codul de testare în directoare separate. Este de dorit ca adăugarea de noi teste la proiect să nu fie o sarcină dificilă și să fie posibilă rularea tuturor testelor. Unele sisteme de control al versiunilor, cum ar fi git, suportă cârlige ( English hook ), cu care puteți configura lansarea tuturor testelor înainte de a efectua modificări. Dacă cel puțin unul dintre teste eșuează, modificările nu vor fi comise. De asemenea, pot fi aplicate sisteme de integrare continuă .
Există instrumente și biblioteci de testare unitară pentru cele mai populare limbaje de programare de nivel înalt. Unii dintre ei:
Unele limbi au suport pentru testarea unitară la nivel de sintaxă. Acest lucru elimină nevoia de a alege la ce cadru să se conecteze și facilitează portarea codului către alte proiecte.
Un exemplu de astfel de limbi:
Exemplu de cod în limbajul D
clasa ABC { this () { val = 2 ; } private int val ; public func () { val *= 2 ; } } unittest { ABC a ; a . func (); assert ( a . val > 0 && a . val < 555 ); // puteți accesa o variabilă privată în interiorul modulului }