De ce ar trebui să aplicați și procese de cod curat pentru a testa codul

Majoritatea dezvoltatorilor depun mult efort în crearea unei structuri de cod bune. Aceasta înseamnă numirea claselor și metodelor în mod consecvent, plasarea lor în pachetele potrivite, gruparea lor corectă și adoptarea unor procese bune.

Reguli similare de cod curat ar trebui să se aplice și codului de test, dar această categorie este adesea trecută cu vederea.

Drept urmare, dezvoltatorii au adesea opinii diferite cu privire la modul de scriere a testelor. Opiniile diferite nu sunt întotdeauna un lucru rău, dar dacă doriți să adăugați o anumită comandă la baza dvs. de testare, acest articol vă poate ajuta.

În acest articol, voi examina câteva dintre liniile directoare pe care le urmăm pentru a menține codul de testare organizat atunci când lucrăm la proiecte Java. Este posibil ca aceste linii directoare să nu funcționeze întotdeauna pentru proiectul sau echipa dvs. specifică, dar totuși merită luate în considerare atunci când începeți următorul proiect.

procese

Testele separate

Clasificare

Înainte de a separa testele, trebuie să identificăm diferitele categorii. În teorie, acest lucru ar trebui să fie ușor: există teste unitare, teste de integrare și teste end-to-end. Tot ce trebuie să faceți este să le separați. Cu toate acestea, există zone gri. Unele teste de integrare pot fi grupate cu teste unitare, iar unele teste unitare pot utiliza dependență externă, dar totuși pot fi etichetate teste unitare.

În Blogul de testare Google, există un articol despre aruncarea denumirii standard pentru teste și introducerea testelor „mici”, „medii” și „mari”. Nu mergem la aceste extreme, dar viteza testului este un bun indicator al modului în care ar trebui clasificat. Poți decide cât de strict vrei să fii. De obicei, separăm testele după cum urmează:

Separare

Testele de unitate și de integrare sunt plasate împreună și separate prin denumire. Clasele de teste unitare se termină cu * Test.java; testele de integrare se încheie cu * IT.java. Acest lucru permite partajarea utilităților de testare între teste de integrare și teste unitare, dar deoarece testele de integrare sunt mai lente, este incomod să le rulați în mod repetat. JUnit 5 va accepta etichete și va deschide noi posibilități în viitor. Deocamdată, dacă utilizați JUnit 4, există alte modalități de a rula testele.

În mod implicit, pluginul Maven Failsafe recunoaște **/IT * .java, **/* IT.java, și **/* ITCase.java (sau poate fi configurat la). Trebuie pur și simplu să activați acest plugin adăugându-l la pom.xml și rulați comanda „mvn verifica”. IntelliJ nu acceptă separarea testului din cutie, dar poate fi configurat cu ușurință prin crearea a două configurații de rulare:

Toate setările sunt implicite - doar modelele sunt diferite. Acestea pot fi reglate pentru a acoperi mai multe cazuri, dar pentru majoritatea cazurilor, aceste modele simple regex ar trebui să funcționeze:

Testele de sistem nu necesită, de obicei, accesarea codului sursă al aplicației. Sunt scrise așa cum sistemul va fi consumat de alte sisteme. De exemplu, dacă este un serviciu REST, atunci aceste teste ar trimite cereri HTTP pentru afirmarea răspunsurilor. Acestea ar trebui plasate într-o sursă separată rădăcină sau - chiar mai bine - într-un proiect separat. Prin urmare, nu este necesară nicio lucrare suplimentară pentru a le rula separat.

Păstrarea codului acoperit

Chiar și atunci când echipa dvs. decide să aibă o acoperire bună a testelor, nu este întotdeauna ușor să respectați acest obiectiv. Pe măsură ce proiectele progresează sau pe măsură ce echipa crește, logica importantă poate ajunge să fie descoperită de teste (trebuie să faceți o soluție rapidă, este sfârșitul sprintului și caracteristicile nu sunt încă finalizate etc.).

Pentru a menține codul bine testat, cel mai important lucru este să înțelegem în comun echipa. Dacă membrii echipei nu doresc să scrie teste sau nu văd avantajul utilizării lor, nicio sumă de sfaturi sau strategii nu vă va ajuta.

Păstrarea testelor curate

Poate părea evident, dar dezvoltatorii încearcă adesea să refactorizeze codul de producție și uită că aceleași reguli de cod curat se aplică și codului de testare. Dacă codul de testare este o mizerie, modificările pentru clasă sau funcție nu vor fi testate - nimeni nu va dori să atingă codul. Pentru astfel de teste, dezvoltatorii schimbă de obicei codul de producție și apoi încearcă să remedieze testele nereușite schimbând afirmațiile pentru a se aștepta la un comportament nou.

Odată, a trebuit să actualizez unele logici ale clientului serviciului web, am găsit o logică ciudată și m-am gândit că, făcându-l să arate „în mod corect”, am îmbunătățit codul. S-a dovedit că serviciul web era buggy. A funcționat doar cu acele valori ciudate, dar pentru că nu a fost clar în test, am crezut că este o greșeală - atât în ​​test cât și în codul de producție.

Rulați întotdeauna teste

Există momente în care poate fi necesar să ignorați temporar unele teste. Este rar, dar se întâmplă. Dacă ignorați testul prea mult timp, logica de afaceri se poate schimba atât de mult încât este mai ușor să ștergeți testul și să îl uitați.

Aplicarea acoperirii codului

Acest lucru ar putea arăta ca cel mai simplu mod de a obține o acoperire bună. Vă decideți asupra unui procent (se recomandă 70-90 la sută) și nu reușește să construiască dacă acoperirea scade sau urmăriți aceste cerințe în SonarQube. Această metodă este adesea utilizată de clienți atunci când doresc să preia suport pentru departamentele lor IT după finalizarea proiectului. Problema cu această metodă este că îi obligă pe dezvoltatori să atingă un anumit obiectiv, dar nu specifică cum. Acest lucru poate duce la teste neglijent sau teste scrise doar pentru acoperire.

Folosirea cererilor de extragere

Această metodă este similară cu acoperirea forțată, dar în loc să folosească un instrument de scanare a codului, folosește echipa în sine. Este o idee bună să aveți câteva reguli comune și să fiți de acord ca fiecare membru al echipei să le verifice în cererile de extragere. De exemplu, „Dacă eroarea este remediată, atunci ar trebui să existe un test pentru cazul care a cauzat eroarea.” Acest lucru nu numai că încurajează o mai bună acoperire a codului, ci asigură că testele pot fi revizuite în aceeași cerere de extragere.

Crearea obiectelor

A avea cursuri mici și simple nu este întotdeauna ușor (sau chiar posibil), mai ales atunci când trebuie să lucrați cu servicii web complexe. Adesea, doar pentru a executa metoda fără „pointer nul” sau alte excepții, trebuie să construiți un obiect complex și să-l completați cu valorile necesare. Crearea unor astfel de obiecte în teste ocupă multe linii de cod și poate ascunde adevărata intenție a testului. Există diferite moduri de a remedia acest lucru, în funcție de dimensiunea clasei, dacă puteți schimba codul sursă al clasei obiect și cât de des va fi utilizat acest cod.

Folosind metode din fabrică

Una dintre cele mai simple modalități de a simplifica crearea obiectelor este folosirea metodelor din fabrică. Este o tehnică descrisă în carte, Java eficientă. Intenția este de a face cursurile mai ușor de inițializat și, în același timp, de a simplifica crearea datelor de testare. Ideea este că, în loc să apelați constructorul și setatorii asociați, creați o metodă statică care grupează apelurile împreună. Dacă utilizați structuri de date imuabile, înlocuiți mai mulți constructori cu aceste metode care au un nume semnificativ.

Crearea obiectelor în teste

Această abordare este similară cu metodele din fabrică. Poate fi aplicat atunci când nu doriți să actualizați codul de producție cu metodele din fabrică sau când datele inițializate sunt destinate numai unui mediu de testare. Inițializarea obiectelor este simplificată prin crearea de metode de constructor direct în metodă sau într-o clasă partajată. Acest lucru face ca faza „dată” a testului să fie mai ușor de înțeles, deoarece doar datele importante sunt vizibile. De exemplu:


Aveți grijă să nu scrieți metode de inițializare prea generalizate. În următorul caz, nu este clar ce tip de date au fost inițializate:

Crearea de constructori de date de testare (modelul Object Mother)

Dacă aveți experiență în scrierea codului care consumă servicii SOAP, probabil ați întâlnit structuri complexe de clase stratificate, unde, pentru a găsi un cont după nume, trebuie să creați o instanță de Cerere de servicii, apoi AccountListRequest, apoi AccountListRequestQuery, și așa mai departe. Sau, poate, sistemul dvs. în sine funcționează cu clase dificil de inițializat pe deplin.

Este aceeași problemă din nou: o mulțime de linii de cod doar pentru a crea o instanță a unui obiect - de data aceasta, una mai extremă. În acest caz, un model „Mama obiect” vă poate ajuta. Aceste clase inițializează obiecte cu unele valori implicite, apoi dacă este o structură modificabilă, puteți adăuga date specifice pentru cazul de testare.

Deoarece fabricile de obiecte ar putea fi utilizate de mai multe teste unitare, ar trebui să aveți grijă să nu vă bazați pe datele inițializate în ele. De exemplu, dacă „fabrica de conturi” inițializează un cont cu numele „James”, nu ar trebui să afirmați această valoare în test, deoarece nu este clar de ce vă așteptați la acest nume. Este o idee mai bună să modificați pur și simplu numele pentru testul individual sau să creați o fabrică de obiecte mai inteligentă, unde puteți modifica datele implicite. De exemplu:

Lucruri generale

Denumirea metodelor de testare

Numele testelor în Java trebuie să respecte regulile de numire a metodelor și nu pot avea spații. Nu este ușor să descrieți intenția testului în timp ce respectați aceste reguli. Există diferite abordări: separarea fiecărui cuvânt cu un subliniat, („MethodName_It_Should_Work”); separând doar descrierea cu un subliniat, („MethodName_ItShouldWork”); sau chiar scrierea unui model complet „dat când atunci”, („Date_Utilizator_Logat În_Când_Clicuri_Logare_Atunci_LogareUtilizator”).

Puteți alege orice echipă vă este confortabilă. În proiectele noastre, avem tendința de a evita denumirile lungi ale metodelor, deoarece în cazul șarpelui sau al cămilei, acestea sunt încă dificil de citit și scris. De asemenea, corpul de testare descrie de obicei fazele „date când atunci”. Pe larg, modelul nostru de testare poate fi descris în acest format:

Prima parte este pur și simplu un nume de metodă fără prefixul „test”. A doua parte descrie ce stare este testată. Ar putea fi un caz simplu de cale fericită sau o descriere mai concretă. De exemplu: „login_InvalidToken”, „login_WrongPassword”, "login_Success".

Scrierea afirmațiilor

În majoritatea proiectelor noastre, folosim biblioteca de afirmații fluente, AfirmăJ. Este ușor de înțeles și poate fi învățat rapid. În comparație cu afirmațiile JUnit, are un suport excelent Java 8, în special afirmând excepții aruncate sau o listă de rezultate:

Mai multe exemple pot fi găsite pe site-ul oficial AssertJ.

Importuri statice

„Importurile statice” este o caracteristică care poate părea convenabilă, dar care poate scăpa rapid de sub control. Chiar și documentația oficială Java sugerează utilizarea cu ușurință. Cu toate acestea, în contextul testelor, tindem să iertăm utilizarea importurilor statice. Pentru metodele de afirmare, este perfect normal și ar fi ciudat dacă ar trebui să repetați „Assertions.assertThat ()” în loc de doar „AssertThat ()”. Poate doriți să luați în considerare utilizarea altor importuri statice (potrivitoare Hamcrest, metode Mockito, metode de integrare Spring etc.). De obicei, atunci când aveți un test de integrare mai complex, devine dificil de urmat de unde metoda a fost importată static. Uneori s-ar putea să apară chiar erori de compilare, deoarece două metode importate static nu funcționează împreună. Prin urmare, este o practică bună să evitați majoritatea importurilor statice, cu excepția afirmațiilor.

Gânduri finale

O bună structură de cod este o practică importantă pentru dezvoltatori, dar este, de asemenea, important să nu uitați să aplicați aceste practici pentru a testa codul. Sperăm că ați găsit câteva dintre liniile directoare utile. Care sunt unele dintre practicile de structură? Simțiți-vă liber să distribuiți comentariile de mai jos.