Dezvoltare bazată pe teste

Versiunea actuală a paginii nu a fost încă revizuită de colaboratori experimentați și poate diferi semnificativ de versiunea revizuită la 31 octombrie 2018; verificările necesită 11 modificări .

Dezvoltarea bazată pe teste (TDD ) este o tehnică de dezvoltare software  care se bazează pe repetarea ciclurilor de dezvoltare foarte scurte: mai întâi, este scris un test care acoperă modificarea dorită, apoi este scris cod care va permite testului să treacă și, în final, se realizează refactorizarea. a realizat un nou cod la standardele relevante. Kent Beck , considerat inventatorul tehnicii, a susținut în 2003 că dezvoltarea bazată pe teste încurajează designul simplu și inspiră încredere [ 1 ] . 

În 1999, când a apărut, dezvoltarea bazată pe teste a fost strâns legată de conceptul test-first utilizat  în programarea extremă [2] , dar mai târziu a apărut ca o metodologie independentă. [3] .

Un test este o procedură care vă permite fie să confirmați, fie să infirmați funcționalitatea codului. Când un programator verifică funcționalitatea codului pe care l-a dezvoltat, el efectuează testarea manuală.

Cerințe

Dezvoltarea bazată pe teste necesită ca dezvoltatorul să creeze teste unitare automate care definesc cerințele pentru cod chiar înainte de a scrie codul real. Un test conține teste de condiție care pot fi îndeplinite sau nu. Când sunt executate, se spune că testul a trecut. Trecerea testului confirmă comportamentul intenționat de programator. Dezvoltatorii folosesc adesea cadre de testare pentru a crea și automatiza lansarea suitelor de testare .  În practică, testele unitare acoperă părți critice și netriviale ale codului. Acesta poate fi cod care se modifică frecvent, cod care face ca multe alte coduri să funcționeze sau cod care are multe dependențe.

Mediul de dezvoltare trebuie să răspundă rapid la mici modificări ale codului. Arhitectura programului ar trebui să se bazeze pe utilizarea multor componente cu un grad ridicat de coeziune internă, care sunt slab cuplate între ele, ceea ce facilitează testarea codului.

TDD nu implică doar verificarea corectitudinii, ci afectează și proiectarea programului. Pe baza testelor, dezvoltatorii își pot imagina rapid de ce funcționalitate are nevoie utilizatorul. Astfel, detaliile interfeței apar cu mult înainte de implementarea finală a soluției.

Desigur, aceleași cerințe ale standardelor de codare se aplică testelor ca și codului principal.

Ciclu de dezvoltare bazat pe teste

Acest flux de lucru se bazează pe cartea Test Driven Development: By Example de Kent Beck .  [unu]

Adăugarea unui test

Când se dezvoltă prin testare, adăugarea fiecărei funcționalități noi (funcție de ing .  ) la program începe cu scrierea unui test. Inevitabil, acest test va eșua deoarece codul corespunzător nu a fost încă scris. (Dacă testul scris trece, fie funcționalitatea „nouă” propusă există deja, fie testul are defecte.) Pentru a scrie un test, un dezvoltator trebuie să înțeleagă clar cerințele pentru noua caracteristică. Pentru aceasta, sunt luate în considerare posibile cazuri de utilizare și povești de utilizatori. Noile cerințe pot modifica și testele existente. Acest lucru distinge dezvoltarea bazată pe teste de tehnicile în care testele sunt scrise după ce codul a fost deja scris: forțează dezvoltatorul să se concentreze pe cerințe înainte de a scrie codul - o diferență subtilă, dar importantă.

Rularea tuturor testelor: asigurați-vă că testele noi eșuează

În această etapă, se verifică dacă testele abia scrise nu trec. Această etapă verifică și testele în sine: proba scrisă poate întotdeauna să treacă și, prin urmare, să fie inutilă. Noile teste ar trebui să eșueze din motive evidente. Acest lucru va crește încrederea (deși nu va garanta complet) că testul testează de fapt ceea ce a fost conceput pentru a face.

Scrie codul

În această etapă, se scrie un nou cod, astfel încât testul să treacă. Acest cod nu trebuie să fie perfect. Este acceptabil ca acesta să treacă testul într-un mod neelegant. Acest lucru este acceptabil deoarece pașii următori îl vor îmbunătăți și lustrui.

Este important să scrieți cod conceput special pentru a trece testul. Nu ar trebui să adăugați funcționalități inutile și, în consecință, netestate.

Rularea tuturor testelor: asigurați-vă că toate testele trec

Dacă toate testele trec, programatorul poate fi sigur că codul îndeplinește toate cerințele testate. După aceea, puteți trece la etapa finală a ciclului.

Refactorizare

Când funcționalitatea necesară este atinsă, codul poate fi curățat în această etapă. Refactorizarea  este procesul de modificare a structurii interne a unui program fără a afecta comportamentul său extern și cu scopul de a facilita înțelegerea activității sale, eliminând duplicarea codului și ușurând efectuarea modificărilor în viitorul apropiat.

Repetați ciclul

Ciclul descris se repetă, implementând din ce în ce mai multe funcționalități noi. Pașii ar trebui să fie mici, între 1 și 10 modificări între teste. Dacă noul cod nu reușește noile teste sau dacă testele vechi nu mai trec, programatorul trebuie să revină la depanare . Când utilizați biblioteci terță parte, nu ar trebui să faceți modificări atât de mici încât să testeze literalmente biblioteca terță parte [3] , și nu codul care o folosește, cu excepția cazului în care există suspiciunea că biblioteca conține erori.

Stilul de dezvoltare

Dezvoltarea bazată pe teste este strâns legată de principii precum „ păstrați-l simplu, stupid, KISS ” și „ nu veți avea nevoie de el, YAGNI.  Designul poate fi mai curat și mai clar scriind doar codul necesar pentru a trece testul. [1] Kent Beck sugerează, de asemenea, principiul „ fake it till you make it. Testele trebuie scrise pentru funcționalitatea testată. Se consideră că acest lucru are două avantaje. Acest lucru vă ajută să vă asigurați că aplicația este testabilă, deoarece dezvoltatorul va trebui să se gândească la modul în care va fi testată aplicația de la bun început. De asemenea, ajută la asigurarea faptului că toate funcționalitățile sunt acoperite de teste. Când o caracteristică este scrisă înainte de testare, dezvoltatorii și organizațiile tind să treacă la următoarea caracteristică fără a o testa pe cea existentă.   

Ideea de a verifica dacă un test nou scris eșuează ajută la asigurarea faptului că testul testează ceva. Numai după această verificare ar trebui să începeți implementarea noii funcționalități. Această tehnică, cunoscută sub numele de „roșu/verde/refactorizare”, este denumită „mantra de dezvoltare condusă de testare”. Aici, roșu înseamnă cei care nu au trecut testele, iar verde înseamnă cei care au trecut.

Practicile consacrate de dezvoltare bazată pe teste au condus la crearea tehnicii de dezvoltare bazată pe test de acceptare (ATDD ), în care criteriile descrise de client sunt automatizate în teste de acceptare, care sunt apoi utilizate în procesul obișnuit de dezvoltare prin teste unitare ( ing  . .unit test-driven development, UTDD ). [4] Acest proces asigură că aplicația îndeplinește cerințele menționate. Când se dezvoltă prin teste de acceptare, echipa de dezvoltare se concentrează pe un obiectiv clar: să satisfacă testele de acceptare care reflectă cerințele relevante ale utilizatorului.  

Teste de acceptare (funcționale) ( teste de client în limba engleză  , teste de acceptare ) - teste care verifică funcționalitatea aplicației pentru conformitatea cu cerințele clienților. Testele de acceptare sunt efectuate de partea clientului. Acest lucru îl ajută să fie sigur că va obține toate funcționalitățile necesare.

Beneficii

Un studiu din 2005 a arătat că utilizarea dezvoltării bazate pe teste înseamnă a scrie mai multe teste și că programatorii care scriu mai multe teste tind să fie mai productivi. [5] Ipotezele care leagă calitatea codului de TDD au fost neconcludente. [6]

Programatorii care folosesc TDD în proiecte noi raportează că este mai puțin probabil să simtă nevoia să folosească un depanator. Dacă unele dintre teste eșuează brusc, revenirea la cea mai recentă versiune care trece toate testele poate fi mai productivă decât depanarea. [7]

Dezvoltarea bazată pe teste oferă mai mult decât validare, ea influențează și proiectarea programului. Prin concentrarea inițială pe teste, este mai ușor să ne imaginăm de ce funcționalitate are nevoie utilizatorul. Astfel, dezvoltatorul se gândește la detaliile interfeței înainte de implementare. Testele vă obligă să vă faceți codul mai testabil. De exemplu, abandonați variabilele globale, singleton-urile, faceți clasele mai puțin cuplate și mai ușor de utilizat. Codul puternic cuplat sau codul care necesită inițializare complexă va fi mult mai dificil de testat. Testarea unitară contribuie la formarea de interfețe clare și mici. Fiecare clasă va avea un rol specific, de obicei unul mic. În consecință, implicarea între clase va scădea și conectivitatea va crește. Programarea contractului ( ing.  design by contract ) completează testarea, formând cerințele necesare prin declarații ( ing.  assertions ).

Deși dezvoltarea bazată pe teste necesită scris mai mult cod, timpul general de dezvoltare este de obicei mai mic. Testele protejează împotriva erorilor. Prin urmare, timpul petrecut pentru depanare este redus de multe ori. [8] Un număr mare de teste ajută la reducerea numărului de erori din cod. Remedierea defectelor mai devreme în dezvoltare previne erorile cronice și costisitoare care duc la o depanare lungă și plictisitoare mai târziu.

Testele vă permit să refactorizați codul fără riscul de a-l da peste cap. Când faceți modificări la codul bine testat, riscul de a introduce noi erori este mult mai mic. Dacă noua funcționalitate duce la erori, testele, dacă există, desigur, vor arăta imediat acest lucru. Când lucrați cu cod pentru care nu există teste, o eroare poate fi descoperită după un timp considerabil, când va fi mult mai dificil să lucrați cu codul. Codul bine testat tolerează cu ușurință refactorizarea. Încrederea că schimbările nu vor distruge funcționalitatea existentă oferă dezvoltatorilor încredere și le crește eficiența. Dacă codul existent este bine acoperit de teste, dezvoltatorii se vor simți mult mai liberi să ia decizii arhitecturale care îmbunătățesc designul codului.

Dezvoltarea bazată pe teste încurajează un cod mai modular, flexibil și extensibil. Acest lucru se datorează faptului că, cu această metodologie, dezvoltatorul trebuie să se gândească la program ca multe module mici care sunt scrise și testate independent și abia apoi conectate împreună. Acest lucru are ca rezultat clase mai mici, mai specializate, mai puține cupluri și interfețe mai curate. Utilizarea modelelor contribuie, de asemenea, la modularizarea codului, deoarece necesită un mecanism simplu pentru a comuta între clasele simulate și cele obișnuite.

Deoarece este scris doar codul necesar pentru a trece testul, testele automate acoperă toate căile de execuție. De exemplu, înainte de a adăuga o nouă instrucțiune condiționată, dezvoltatorul trebuie să scrie un test care motivează adăugarea acestei instrucțiuni condiționate. Ca rezultat, testele care rezultă din dezvoltarea bazată pe teste sunt destul de complete: detectează orice modificări neintenționate în comportamentul codului.

Testele pot fi folosite ca documentație. Un cod bun vă va spune cum funcționează mai bine decât orice documentație. Documentația și comentariile din cod pot fi învechite. Acest lucru poate fi confuz pentru dezvoltatorii care se uită la cod. Și întrucât documentația, spre deosebire de teste, nu poate spune că este depășită, situațiile în care documentația nu este adevărată nu sunt neobișnuite.

Puncte slabe

Vizibilitatea codului

Suita de testare trebuie să aibă acces la codul testat. Pe de altă parte, principiile încapsulării și ascunderii datelor nu ar trebui încălcate. Prin urmare, testele unitare sunt de obicei scrise în aceeași unitate sau proiect ca și codul testat.

Este posibil să nu existe acces la câmpuri și metode private din codul de testare .  Prin urmare, testarea unitară poate necesita muncă suplimentară. În Java , un dezvoltator poate folosi reflectarea pentru a se referi la câmpurile marcate ca private . [10] Testele unitare pot fi implementate în clasele interne, astfel încât acestea să aibă acces la membrii clasei exterioare. În .NET Framework , clasele parțiale pot fi folosite pentru a accesa câmpuri și metode private dintr-un test.   

Este important ca fragmentele de cod destinate exclusiv testării să nu rămână în codul lansat. În C , directivele de compilare condiționată pot fi folosite pentru aceasta. Cu toate acestea, acest lucru va însemna că codul lansat nu se potrivește exact cu codul testat. Rulând sistematic teste de integrare pe o versiune lansată, vă puteți asigura că nu rămâne niciun cod care să se bazeze implicit pe diverse aspecte ale testelor unitare.

Nu există un consens în rândul programatorilor care utilizează dezvoltarea bazată pe teste cu privire la cât de semnificativ este să testeze metode private și protejate , precum și datele .  Unii sunt convinși că este suficient să testați orice clasă doar prin interfața sa publică, deoarece variabilele private sunt doar un detaliu de implementare care se poate schimba, iar modificările acesteia nu ar trebui să fie reflectate în suita de teste. Alții susțin că aspecte importante ale funcționalității pot fi implementate în metode private, iar testarea lor implicit printr-o interfață publică nu va face decât să complice lucrurile: testarea unitară presupune testarea celor mai mici unități posibile de funcționalitate. [11] [12]

Teste false, simulate și de integrare

Testele unitare testează fiecare unitate individual. Nu contează dacă modulul conține sute de teste sau doar cinci. Testele utilizate în dezvoltarea bazată pe teste nu trebuie să depășească granițele procesului, să folosească conexiuni de rețea. În caz contrar, trecerea testelor va dura mult timp, iar dezvoltatorii vor avea mai puține șanse să ruleze întreaga suită de teste. Introducerea unei dependențe de module sau date externe transformă și testele unitare în teste de integrare. În același timp, dacă un modul din lanț se comportă incorect, este posibil să nu fie imediat clar care dintre ele.

Când codul în curs de dezvoltare utilizează baze de date, servicii web sau alte procese externe, este logic să evidențiezi partea acoperită de testare. Acest lucru se face în doi pași:

  1. Oriunde este necesar accesul la resurse externe, trebuie declarată o interfață prin care se va realiza acest acces. Consultați principiul inversării dependenței pentru o discuție despre beneficiile acestei abordări, indiferent de TDD . 
  2. O interfață trebuie să aibă două implementări. Primul, care oferă de fapt acces la resursă, iar al doilea, care este un obiect fals sau simulat . Tot ceea ce fac obiectele false este să adauge mesaje precum „Obiect persoană salvat” în jurnal, astfel încât să poată verifica ulterior comportamentul corect. Obiectele simulate diferă de obiectele false prin faptul că ele însele conțin afirmații care verifică comportamentul codului testat . Metodele obiectelor false și simulate care returnează date pot fi configurate pentru a returna aceleași date credibile atunci când sunt testate. Ele pot emula erori, astfel încât codul de gestionare a erorilor să poată fi testat în detaliu. Alte exemple de servicii false utile în dezvoltarea bazată pe teste sunt: ​​un serviciu de codificare care nu codifică date, un generator de numere aleatorii care returnează întotdeauna unul. Implementările false sau simulate sunt exemple de injectare a dependenței .  

Utilizarea de obiecte false și simulate pentru a reprezenta lumea exterioară are ca rezultat ca baza de date reală și alt cod exterior să nu fie testate ca urmare a procesului de dezvoltare bazat pe teste. Pentru a evita erorile, sunt necesare teste ale implementărilor reale ale interfețelor descrise mai sus. Aceste teste pot fi separate de restul testelor unitare și sunt într-adevăr teste de integrare. Au nevoie de mai puține decât cele modulare și pot fi lansate mai rar. Cu toate acestea, cel mai adesea acestea sunt implementate folosind același cadru de testare ca și testele unitare . 

Testele de integrare care modifică datele din baza de date ar trebui să returneze baza de date la starea în care se afla înainte de rularea testului, chiar dacă testul eșuează. Următoarele tehnici sunt adesea folosite pentru aceasta:

Există biblioteci Moq, jMock, NMock, EasyMock, Typemock, jMockit, Unitils, Mockito, Mockachino, PowerMock sau Rhino Mocks, precum și sinon pentru JavaScript, concepute pentru a simplifica procesul de creare a obiectelor simulate.

Vezi și

Note

  1. 1 2 3 Beck, K. Test-Driven Development by Example, Addison Wesley, 2003
  2. Lee Copeland. programare extremă . Computerworld (decembrie 2001). Data accesului: 11 ianuarie 2011. Arhivat din original pe 27 august 2011.
  3. 1 2 Newkirk, JW și Vorontsov, AA. Dezvoltare bazată pe teste în Microsoft .NET , Microsoft Press, 2004
  4. Koskela, L. „Test Driven: TDD and Acceptance TDD for Java Developers”, Manning Publications, 2007
  5. Erdogmus, Hakan; Morisio, Torchiano. Despre eficacitatea abordării de programare prin testare (link indisponibil) . Proceedings of the IEEE Transactions on Software Engineering, 31(1). ianuarie 2005. (NRC 47445). - „Am descoperit că studenții primii testului au scris în medie mai multe teste și, la rândul lor, studenții care au scris mai multe teste au avut tendința de a fi mai productivi.” Consultat la 14 ianuarie 2008. Arhivat din original la 27 august 2011. 
  6. Proffitt, Jacob TDD S-a dovedit eficient! Sau este? (link indisponibil) . — „Deci, relația TDD cu calitatea este problematică în cel mai bun caz. Relația sa cu productivitatea este mai interesantă. Sper că există un studiu de urmărire pentru că cifrele de productivitate pur și simplu nu se adună foarte bine pentru mine. Există o corelație incontestabilă între productivitate și numărul de teste, dar această corelație este de fapt mai puternică în grupul non-TDD (care a avut un singur valori abere, comparativ cu aproximativ jumătate din grupul TDD fiind în afara benzii de 95%)". Consultat la 21 februarie 2008. Arhivat din original pe 27 august 2011. 
  7. Llopis, Noel Stepping Through the Looking Glass: Test-Driven Game Development (Partea 1) (link nu este disponibil) . Jocuri din interior (20 februarie 2005). - „Comparând [TDD] cu abordarea de dezvoltare non-test-driven, înlocuiți toate verificările mentale și depanarea cu cod care verifică că programul dumneavoastră face exact ceea ce ați intenționat să facă.” Consultat la 1 noiembrie 2007. Arhivat din original la 22 februarie 2005. 
  8. Müller, Matthias M.; Padberg, Frank. Despre rentabilitatea investiției din dezvoltarea testată (PDF)  (link indisponibil) 6. Universität Karlsruhe, Germania. Consultat la 1 noiembrie 2007. Arhivat din original pe 27 august 2011.
  9. Loughran, Steve Testing (PDF). HP Laboratories (6 noiembrie 2006). Preluat la 12 august 2009. Arhivat din original la 27 august 2011.
  10. Burton, Ross Subversing Java Access Protection for Unit Testing . O'Reilly Media Inc. (12.11.2003). Preluat la 12 august 2009. Arhivat din original la 27 august 2011.
  11. Newkirk, James Testarea metodelor private/variabilelor membrilor - Ar trebui sau nu ar trebui . Microsoft Corporation (7 iunie 2004). Preluat la 12 august 2009. Arhivat din original la 27 august 2011.
  12. Stall, Tim Cum se testează metodele private și protejate în .NET . CodeProject (1 martie 2005). Preluat la 12 august 2009. Arhivat din original la 27 august 2011.

Literatură

Link -uri