Introducere în testare
Publicat: 2021-06-16Bun venit la noua noastră serie de bloguri despre tot ce ține de testare . Sperăm că aceste postări pe blog vă vor oferi o idee generală despre cum le scriem aici la Mediatoolkit și de ce. Înainte de a ne aprofunda în exemple, să începem cu câteva definiții de bază, idei și beneficii ale scrierii testelor.
Ce fel de teste sunt acolo?
Nu toate testele sunt create egale. Există diferite tipuri de teste pentru diferite scopuri. Deși această postare se concentrează în principal pe testele unitare , ar trebui să fim conștienți de diferențele și să înțelegem beneficiile fiecăruia.
1. Teste manuale
Testarea manuală implică teste executate manual de un tester. Acestea sunt de obicei realizate de Asigurarea Calității sau de dezvoltatorii înșiși . QA ar trebui văzută ca ultima linie de apărare, nu principalul tester al aplicației (QA care face testarea manuală ca modalitate principală de testare a UI este o excepție validă din acest caz).
De obicei, dezvoltatorii scriu teste manuale atunci când doresc să ruleze unele lucruri la nivel local și să vadă cum se comportă sistemul fără a împinge niciun cod în faza de instalare sau Dumnezeu să te salveze, producție. Cu toate acestea, scopul acestor teste nu este robustețea codului. Singurul rol al acestor teste este de a detecta erori și de a asigura calitatea produsului dumneavoastră .
2. Teste de integrare
Sistemele sunt compuse din mai multe componente, sau cel puțin ar trebui să fie. Testele de integrare verifică API-ul public al acestor componente. Nu doar API-ul REST, ci orice API expus public, cum ar fi comunicarea subiectului Kafka.
Dacă o componentă „spune” că emite un mesaj pe un subiect într-un anumit format, acesta este API-ul sau contractul său public. Testarea integrării este verificarea dacă mai multe componente dezvoltate individual pot comunica și dacă contractele lor se potrivesc ca grup.
Dacă testați aceste componente doar individual, comportamentul lor individual ar putea funcționa corect, dar când încercați să le conectați într-un grup mai mare, descoperiți că contractele lor diferă .
3. Teste end-to-end (E2E).
Testarea de la capăt la capăt asigură fluxul experienței utilizatorului a produsului final . Complexitatea sistemelor de astăzi este greu de acoperit cu teste. Multe sisteme se bazează unul pe celălalt, iar testele E2E asigură că produsul face ceea ce se așteaptă . QA validează corectitudinea produsului dvs. parcurgând fluxul utilizatorilor finali și verificând dacă toate sistemele se comportă conform așteptărilor.
4. Teste unitare
Testele unitare sunt coloana vertebrală a oricărui software de încredere . Ele fac baza pentru alte teste. Ei testează unități individuale.
Dezvoltatorii pot greși definiția unei unități cu o singură metodă. Testele unitare ar trebui să testeze comportamentul unei componente care ar putea fi compusă din mai multe clase diferite. Metodele publice accesibile de către alte componente trebuie testate, nu testați metode sau clase protejate. Sunt detalii de implementare, nu fac parte din API-ul dvs. public .
De ce ar trebui să testăm?
Dacă scriem teste bune și le scriem des, putem asigura calitatea produsului nostru înainte ca acesta să vadă lumina zilei.
Pe măsură ce sistemul nostru îmbătrânește, beneficiile testării devin din ce în ce mai evidente. Mediile noastre devin fiabile, economisesc timp de dezvoltare și multă trage de păr atunci când inevitabil lucrurile merg prost. Colegii dvs. vor fi recunoscători când vor putea arunca o privire la testele dvs. și vor înțelege ce ar trebui și ce nu ar trebui să facă codul dvs. fără a fi nevoie să rulați manual lucrurile .
Cod robust
Înainte de a începe să vorbim despre „cod robust”, cum ar trebui să-l definim? Ce face codul robust? Înseamnă asta că ar trebui să programăm în mod defensiv și să ne gândim la modul în care alți dezvoltatori ar putea abuza de codul nostru?
Codul robust este întotdeauna simplu și curat. Simplitatea este robustă, complexitatea este fragilă . Ar trebui să ne ocupăm de intrările nevalide, dar asta nu înseamnă că trebuie să programăm defensiv și să nu avem încredere în echipa noastră.
Legea lui Gall : Un sistem complex care funcționează se constată în mod invariabil că a evoluat dintr-un sistem simplu care a funcționat.
Propoziția inversă pare să fie, de asemenea, adevărată: un sistem complex conceput de la zero nu funcționează niciodată și nu poate fi făcut să funcționeze. Trebuie să iei de la capăt, începând cu un sistem simplu funcțional.
Refactorizați în siguranță
Când codul dvs. este acoperit de teste, nu vă mai temeți să schimbați codul existent . După fiecare modificare, puteți rula testele și vă puteți asigura că nu ați spart lucruri. Când ai teste, nu trebuie să programezi defensiv.
Refactorizarea fără teste coboară pe o pantă alunecoasă care va ajunge în nopți nedormite și duminica lucrătoare. Acest subiect este prea larg pentru a fi tratat aici și merită o postare proprie pe blog în viitor.
Cum să nu scriem teste?
Este la fel de important să știi cum să NU scrieți teste unitare , precum este și cum să le scrieți.
- Scrierea unui test care imprimă rezultatul unui apel de metodă nu este un test deoarece nu validăm rezultatul dorit .
- Dacă testul dvs. citește date dintr-un fișier din folderul Documente, nu este un test unitar real, deoarece testele nu ar trebui să depindă de mediu.
- Orice dezvoltator ar trebui să poată verifica codul dvs. și să ruleze testele cu succes fără a face nimic altceva.
- Fiecare test unitar ar trebui să fie independent de alte teste . Aceasta înseamnă că nici ordinea de execuție a testelor dvs. nu ar trebui să conteze.
- Rularea testelor de mai multe ori ar trebui să se încheie întotdeauna cu aceleași rezultate dacă nu modificăm niciun cod.
- Testele unitare ar trebui să testeze comportamentele , nu apelurile individuale ale metodelor. Nu fiecare clasă și metodă trebuie să fie testată.
Comportamentul este ceva care produce o valoare reală de care are nevoie utilizatorul sistemului tău. Utilizatorul dvs. trebuie să știe dacă productFactory.create() a creat același obiect când a fost apelat de două ori sau dacă depozitul dvs. a fost apelat cu niște parametri? Probabil că nu, dar totuși mulți dezvoltatori scriu exact acest tip de teste.
Dacă testele dvs. arată așa, ele sunt strâns legate de implementarea dvs. De fiecare dată când doriți să modificați detaliile implementării dvs., trebuie să vă actualizați testele, chiar dacă comportamentul este același. Testele dvs. ar trebui să se schimbe numai atunci când se schimbă comportamentul, nu detaliile implementării . Cu alte cuvinte, testați ce face codul dvs., nu cum îl face.
Cum scriem testele?
Testele noastre trebuie să urmeze cele mai bune practici de cod , trebuie să fie independente de mediu și trebuie să se execute rapid .
Este important să păstrați timpul de execuție a testului cât mai scurt posibil. Fiecare test nu ar trebui să dureze mai mult de câteva milisecunde . Când testele durează prea mult pentru a se executa, oamenii tind să le ignore și doar se bazează pe serverul lor CI, cum ar fi Jenkins, pentru a face tam-tam atunci când nu își poate construi executabilele de implementare.
Fiecare test este compus din 3 secțiuni „A” (modelul AAA) :

- Aranja
- act
- Afirma
1. Aranjați
În secțiunea de aranjare a testului nostru, ne asigurăm că sistemul nostru este într-o stare specifică înainte de a apela comportamentul pe care vrem să-l testăm . „Sistemul” ar putea fi un obiect pe care trebuie să-l setăm într-un mod specific pentru a produce un comportament, creând fișiere temporare sau lucruri de această natură.
Codul din această secțiune este de obicei mai mare decât celelalte două combinate .
Un model de design care ar trebui să se dovedească deosebit de util pentru a menține această secțiune mică este Object Mother . Acest model de design este foarte asemănător cu Factory , dar are metode mai specifice care construiesc obiecte preconfigurate pentru dvs. În timp ce o Factory standard ar putea avea o metodă precum createCar(carDescription) , un ObjectMother va avea metode precum createRedFerrari() , createBlackTesla() sau createBrokenYugo() .
2. Act
Această secțiune a testului dvs. trebuie să aibă o singură linie . Această linie execută comportamentul testat. Dacă te trezești să scrii mai multe rânduri pentru această secțiune, probabil că nu ai încapsularea corectă a comportamentului tău . Nu ar trebui să se aștepte ca clienții tăi să apeleze mai multe metode ale obiectului tău într-o anumită ordine, așa că de ce ar face testele tale?
Această linie este un apel de metodă pe care vrem să-l testăm. Dacă această metodă returnează un rezultat, ar trebui să stocați acea valoare într-o variabilă pentru a verifica dacă este valoarea așteptată în pasul Assert.
3. Afirmați
După ce am pregătit sistemul în secțiunea Aranjare și am executat acțiunea noastră testată în secțiunea Act, trebuie să validăm rezultatul acțiunii. De obicei, verificăm rezultatul metodei aici, dar uneori, metodele noastre nu returnează valori, dar totuși produc efecte secundare. Dacă se aștepta ca codul nostru să schimbe starea unui obiect, să creeze un fișier sau să elimine ceva dintr-o List , ar trebui să verificăm dacă a făcut exact asta.
Stubs vs Mocks
Majoritatea dezvoltatorilor folosesc termenii mock și stub în mod interschimbabil, dar există diferențe.
Un ciot nu poate eșua testul, un simulator poate.
Stub -urile sunt bazate pe stare , ele returnează valori codificate. „Ce rezultat am obținut?”
Mock -urile sunt bazate pe comportament , le folosești pentru a verifica modul în care comportamentul tău trece prin ele. „Cum am obținut rezultatul?”
Dacă testele dvs. unitare sunt pline de simulari, testele devin foarte fragile, ceea ce înseamnă că de fiecare dată când schimbați unul dintre detaliile de implementare, trebuie să actualizați toate apelurile simulate.
Testează un singur lucru.
Trebuie să fim capabili să izolăm un comportament și să dovedim că funcționează . Dacă acel comportament ar trebui să funcționeze diferit cu intrări diferite, trebuie să scriem un nou test pentru fiecare dintre aceste comportamente. Este greu de știut de ce testul nostru a eșuat dacă avem un test mare care testează mai multe lucruri simultan. Mai mult, devine mai greu să eliminați funcțiile de care nu mai avem nevoie și să vedem ce caracteristici am rupt atunci când adăugăm cod nou.
Este perfect să aveți mai multe afirmații în secțiunea finală a testelor dvs., atâta timp cât asta nu necesită apelarea comportamentului testat de mai multe ori. Dacă acesta este cazul, va fi greu să identificați comportamentul defectuos și să îl remediați.
Când afirmăm mai multe comportamente într-un singur test, nu obținem o imagine clară a ceea ce exact nu funcționează , deoarece testul va raporta doar primul eșec, iar restul sunt omise. Devine mult mai greu de înțeles ce schimbări sunt necesare și câte lucruri nu funcționează conform așteptărilor.
Teste de denumire
Un lucru care separă testele bune de testele grozave este numele testului. Testele nu ar trebui să ne spună doar ce fac, ci și când o fac.
Există o mulțime de modele bune de denumire pe care le puteți folosi, așa că alegeți-l pe cel care vi se pare cel mai descriptiv și rămâneți cu el. Iată câteva exemple de nume excelente de teste:
- RegistrationServiceShould.createNewAccountWhenEmailIsNotTaken
- RegistrationServiceTest.whenEmailIsFree_createNewAccount
- RegistrationServiceTest.if_freeEmail_when_userCreatesAccount_then_create
Când scrieți teste unitare, este mult mai important să comunicați ceea ce testați decât să urmați cele mai bune practici de numire a metodelor. De exemplu, în Java, folosim camelCase când scriem metode, dar este perfect valid să folosim caracterul de subliniere (_) pentru a separa starea de acțiunea din numele testului.
Teste curate
Testele pe care le scrieți ar trebui să urmeze toate practicile de cod curat pe care le aplicați codului dvs. Testele nu sunt cetățeni de clasa a doua și trebuie să aplicați același nivel de grijă ca și cu restul codului pentru a le face lizibile.
Definirea dublării codului în teste este foarte importantă. Principiul DRY (Don’t Repeat Yourself) se aplică la extragerea comportamentului care se schimbă din același motiv. Testele se schimbă din motive diferite, așa că fiți variat în extragerea lucrurilor din testele dvs. dacă cu adevărat nu se schimbă din același motiv. Alertă spoiler, adesea nu.
if enunţurile nu aparţin testelor. Declarația if ne spune că testul nostru face cel puțin două lucruri diferite și ne-ar fi mai bine dacă ne-am rescrie testul ca două teste diferite. Când sunt denumite corect, va fi mai ușor de înțeles ce fac testele și care sunt toate comportamentele diferite.
Când ar trebui să scriem teste?
Ghidați de principiile TDD, ar trebui să scriem teste înainte de a scrie cod nou .
Când trebuie să adăugăm o nouă caracteristică, mai întâi descriem comportamentul dorit ca un nou test. Facem cele mai puține modificări necesare pentru a trece acel test fără a le întrerupe altele.
În caz contrar, cu cât trece mai mult timp, cu atât avem mai mult cod care nu a fost testat și crește șansa de a introduce erori sau suprainginerie .
De asemenea, testele devin mai complexe, deoarece avem mai mult cod de testat acum și ceea ce se întâmplă de obicei este că dezvoltatorii ajustează testele la cod. Asta înseamnă că ajustăm comportamentul pentru a se potrivi cu ceea ce face codul nostru și nu invers.
Când scriem teste devreme, definiția problemei devine mai mică și este mai ușor să ne gândim la astfel de probleme decât comportamente mai generice și complexe.
Gânduri finale
În timp ce scrierea testelor poate părea un lucru opțional, este esențial să începeți cu baze bune. Codarea este deja dificilă, ușurează-ți tine și colegii tăi scriind cod testabil, care este mai ușor de citit, înțeles și întreținut . În cele din urmă, dacă întâmpinați dificultăți în a face testele să corespundă dorințelor dvs., problema este mai probabilă în codul dvs. decât în teste.
Vă interesează să lucrați la un cod curat și testabil?
Consultați poziția noastră deschisă pentru Dezvoltator Senior Backend
sau trimite-ne o aplicație deschisă !
