8 Cele mai bune practici de testare automată pentru o experiență de testare pozitivă

Publicat: 2022-03-11

Nu este de mirare că mulți dezvoltatori consideră testarea ca pe un rău necesar care slăbește timpul și energia: testarea poate fi plictisitoare, neproductivă și complet prea complicată.

Prima mea experiență cu testarea a fost îngrozitoare. Am lucrat într-o echipă care avea cerințe stricte de acoperire a codului. Fluxul de lucru a fost: implementați o funcție, depanați-o și scrieți teste pentru a asigura acoperirea completă a codului. Echipa nu a avut teste de integrare, ci doar teste unitare cu tone de modele inițializate manual, iar majoritatea testelor unitare au testat mapări manuale banale în timp ce foloseau o bibliotecă pentru a efectua mapări automate. Fiecare test a încercat să afirme fiecare proprietate disponibilă, așa că fiecare schimbare a rupt zeci de teste.

Nu mi-a plăcut să lucrez cu teste, deoarece acestea erau percepute ca o povară care consuma timp. Totuși, nu am renunțat. Testarea de încredere oferă și automatizarea verificărilor după fiecare mică schimbare mi-a stârnit interesul. Am început să citesc și să exersez și am învățat că testele, atunci când sunt făcute corect, pot fi atât utile, cât și plăcute.

În acest articol, împărtășesc opt cele mai bune practici de testare automată pe care mi-aș fi dorit să le cunosc de la început.

De ce aveți nevoie de o strategie de testare automată

Testarea automată este adesea concentrată pe viitor, dar atunci când o implementați corect, beneficiați imediat. Utilizarea instrumentelor care vă ajută să vă faceți treaba mai bine vă poate economisi timp și vă poate face munca mai plăcută.

Imaginați-vă că dezvoltați un sistem care preia comenzile de achiziție din ERP-ul companiei și plasează acele comenzi unui furnizor. Aveți prețul articolelor comandate anterior în ERP, dar prețurile actuale pot fi diferite. Doriți să controlați dacă plasați o comandă la un preț mai mic sau mai mare. Aveți preferințele utilizatorului stocate și scrieți cod pentru a face față fluctuațiilor de preț.

Cum ați verifica dacă codul funcționează conform așteptărilor? Probabil ai:

  1. Creați o comandă inactivă în instanța dezvoltatorului ERP (presupunând că ați configurat-o în prealabil).
  2. Rulați aplicația.
  3. Selectați acea comandă și începeți procesul de plasare a comenzii.
  4. Colectați date din baza de date a ERP.
  5. Solicitați prețurile curente de la API-ul furnizorului.
  6. Înlocuiți prețurile în cod pentru a crea condiții specifice.

V-ați oprit la punctul de întrerupere și puteți merge pas cu pas pentru a vedea ce se va întâmpla pentru un scenariu, dar există multe scenarii posibile:

Preferințe Pretul ERP Prețul vânzătorului Ar trebui să plasăm comanda?
Permiteți prețuri mai mari Permiteți prețuri mai mici
fals fals 10 10 Adevărat
(Aici ar mai exista trei combinații de preferințe, dar prețurile sunt egale, deci rezultatul este același.)
Adevărat fals 10 11 Adevărat
Adevărat fals 10 9 fals
fals Adevărat 10 11 fals
fals Adevărat 10 9 Adevărat
Adevărat Adevărat 10 11 Adevărat
Adevărat Adevărat 10 9 Adevărat

În cazul unei erori, compania poate pierde bani, poate afecta reputația sau ambele. Trebuie să verificați mai multe scenarii și să repetați bucla de verificare de mai multe ori. A face acest lucru manual ar fi plictisitor. Dar testele sunt aici pentru a vă ajuta!

Testele vă permit să creați orice context fără apeluri la API-uri instabile. Ele elimină necesitatea clicurilor repetitive prin interfețele vechi și lente, care sunt prea frecvente în sistemele ERP vechi. Tot ce trebuie să faceți este să definiți contextul pentru unitate sau subsistem și apoi orice depanare, depanare sau explorare a scenariului are loc instantaneu - rulați testul și reveniți la codul dvs. Preferința mea este să configurez o combinație de taste în IDE-ul meu care să repete testul anterior, oferind feedback imediat și automat pe măsură ce fac modificări.

1. Mențineți atitudinea corectă

În comparație cu depanarea manuală și auto-testarea, testele automate sunt mai productive de la bun început, chiar înainte ca orice cod de testare să fie comis. După ce verificați dacă codul dumneavoastră se comportă conform așteptărilor – prin testare manuală sau poate, pentru un modul mai complex, prin parcurgerea acestuia cu un depanator în timpul testării – puteți utiliza aserțiuni pentru a defini ceea ce vă așteptați pentru orice combinație de parametri de intrare.

Odată cu trecerea testelor, ești aproape gata să te angajezi, dar nu tocmai. Pregătiți-vă să refactorizați codul, deoarece prima versiune de lucru de obicei nu este elegantă. Ați efectua acea refactorizare fără teste? Acest lucru este discutabil, deoarece ar trebui să parcurgeți din nou toți pașii manuali, ceea ce v-ar putea diminua entuziasmul.

Dar viitorul? În timp ce efectuează orice refactorizare, optimizare sau adăugare de caracteristici, testele vă ajută să vă asigurați că un modul se comportă în continuare conform așteptărilor după ce îl schimbați, insuflând astfel încredere de durată și permițând dezvoltatorilor să se simtă mai bine pregătiți pentru a aborda lucrările viitoare.

Este contraproductiv să te gândești la teste ca la o povară sau la ceva care îi face fericiți doar pe recenzorii de cod sau pe clienții potențiali. Testele sunt un instrument de care beneficiem noi, ca dezvoltatori. Ne place când codul nostru funcționează și nu ne place să petrecem timp cu acțiuni repetitive sau pe remedierea codului pentru a rezolva erori.

Recent, am lucrat la refactorizarea în baza mea de cod și am cerut IDE using ului meu să curețe directivele neutilizate. Spre surprinderea mea, testele au arătat mai multe eșecuri în sistemul meu de raportare prin e-mail. Cu toate acestea, a fost un eșec valid – procesul de curățare a eliminat unele directive de using din codul meu Razor (HTML + C#) pentru un șablon de e-mail, iar motorul de șabloane nu a reușit să creeze HTML valid ca rezultat. Nu mă așteptam ca o astfel de operațiune minoră să distrugă raportarea prin e-mail. Testarea m-a ajutat să evit să petrec ore întregi prind erori în toată aplicația chiar înainte de lansarea acesteia, când am presupus că totul va funcționa.

Desigur, trebuie să știi să folosești unelte și să nu-ți tai degetele proverbiale. S-ar putea părea că definirea contextului este plictisitoare și poate fi mai dificilă decât rularea aplicației, că testele necesită prea multă întreținere pentru a evita să devină învechite și inutile. Acestea sunt puncte valabile și le vom aborda.

2. Selectați tipul corect de test

Dezvoltatorii ajung adesea să nu le placă testele automate, deoarece încearcă să bată joc de o duzină de dependențe doar pentru a verifica dacă sunt chemați de cod. Alternativ, dezvoltatorii se confruntă cu un test de nivel înalt și încearcă să reproducă fiecare stare a aplicației pentru a verifica toate variațiile într-un modul mic. Aceste modele sunt neproductive și plictisitoare, dar le putem evita utilizând diferite tipuri de teste așa cum au fost destinate. (La urma urmei, testele ar trebui să fie practice și plăcute!)

Cititorii vor trebui să știe ce sunt testele unitare și cum să le scrie și să fie familiarizați cu testele de integrare; dacă nu, merită să se oprească aici pentru a fi la curent.

Există zeci de tipuri de testare, dar aceste cinci tipuri comune fac o combinație extrem de eficientă:

Un set de ilustrații de bază care ilustrează teste unitare, teste de integrare, teste funcționale, teste Canary și teste de încărcare.
Cinci tipuri comune de teste

  • Testele unitare sunt folosite pentru a testa un modul izolat apelând direct metodele acestuia. Dependențele nu sunt testate, prin urmare, sunt batjocorite.
  • Testele de integrare sunt folosite pentru a testa subsistemele. Încă folosiți apeluri directe la metodele proprii ale modulului, dar aici ne pasă de dependențe, așa că nu folosiți dependențe batjocorite - doar module dependente reale (de producție). Puteți utiliza în continuare o bază de date în memorie sau un server web batjocorit, deoarece acestea sunt false de infrastructură.
  • Testele funcționale sunt teste pentru întreaga aplicație, cunoscute și ca teste end-to-end (E2E). Nu folosiți apeluri directe. În schimb, toată interacțiunea trece prin API sau interfața cu utilizatorul - acestea sunt testele din perspectiva utilizatorului final. Cu toate acestea, infrastructura este încă batjocorită.
  • Testele Canary sunt similare cu testele funcționale, dar cu infrastructură de producție și un set mai mic de acțiuni. Sunt folosite pentru a se asigura că aplicațiile nou implementate funcționează.
  • Testele de încărcare sunt similare cu testele Canary, dar cu o infrastructură reală de pregătire și un set și mai mic de acțiuni, care se repetă de multe ori.

Nu este întotdeauna necesar să lucrați cu toate cele cinci tipuri de testare de la început. În cele mai multe cazuri, puteți merge mult cu primele trei teste.

Vom examina pe scurt cazurile de utilizare ale fiecărui tip pentru a vă ajuta să alegeți pe cele potrivite nevoilor dvs.

Teste unitare

Amintiți-vă exemplul cu diferite prețuri și preferințe de manipulare. Este un candidat bun pentru testarea unitară, deoarece ne pasă doar de ceea ce se întâmplă în interiorul modulului, iar rezultatele au ramificații importante de afaceri.

Modulul are o mulțime de combinații diferite de parametri de intrare și dorim să obținem o valoare de returnare validă pentru fiecare combinație de argumente valide. Testele unitare sunt bune la asigurarea validității deoarece oferă acces direct la parametrii de intrare ai funcției sau metodei și nu trebuie să scrieți zeci de metode de testare pentru a acoperi fiecare combinație. În multe limbi, puteți evita duplicarea metodelor de testare prin definirea unei metode, care acceptă argumentele necesare pentru codul dvs. și rezultatele așteptate. Apoi, puteți utiliza instrumentele de testare pentru a furniza diferite seturi de valori și așteptări pentru acea metodă parametrizată.

Teste de integrare

Testele de integrare sunt potrivite pentru cazurile în care sunteți interesat de modul în care un modul interacționează cu dependențele sale, cu alte module sau cu infrastructura. Folosiți în continuare apeluri directe de metodă, dar nu există acces la submodule, așa că încercarea de a testa toate scenariile pentru toate metodele de intrare ale tuturor submodulelor este nepractică.

De obicei, prefer să am un scenariu de succes și un scenariu de eșec per modul.

Îmi place să folosesc teste de integrare pentru a verifica dacă un container de injecție de dependență este construit cu succes, dacă o conductă de procesare sau de calcul returnează rezultatul așteptat sau dacă datele complexe au fost citite și convertite corect dintr-o bază de date sau dintr-o API terță parte.

Teste funcționale sau E2E

Aceste teste vă oferă cea mai mare încredere că aplicația dvs. funcționează, deoarece verifică că aplicația dvs. poate porni cel puțin fără o eroare de rulare. Este puțin mai multă muncă să începeți să testați codul fără acces direct la clasele sale, dar odată ce înțelegeți și scrieți primele câteva teste, veți descoperi că nu este prea dificil.

Rulați aplicația pornind un proces cu argumente de linie de comandă, dacă este necesar, apoi utilizați aplicația așa cum ar face-o clientul potențial: apelând punctele finale API sau apăsând butoane. Acest lucru nu este dificil, chiar și în cazul testării UI: Fiecare platformă majoră are un instrument pentru a găsi un element vizual într-o UI.

Teste Canare

Testele funcționale vă spun dacă aplicația dvs. funcționează într-un mediu de testare, dar cum rămâne cu un mediu de producție? Să presupunem că lucrați cu mai multe API-uri terță parte și doriți să aveți un tablou de bord al stărilor acestora sau doriți să vedeți cum aplicația dvs. gestionează solicitările primite. Acestea sunt cazuri comune de utilizare pentru testele canare.

Acestea funcționează acționând pe scurt asupra sistemului de lucru fără a provoca efecte secundare sistemelor terțe. De exemplu, puteți înregistra un nou utilizator sau puteți verifica disponibilitatea produsului fără a plasa o comandă.

Scopul testelor Canary este de a se asigura că toate componentele majore lucrează împreună într-un mediu de producție, fără a eșua din cauza, de exemplu, probleme de acreditări.

Teste de sarcină

Testele de încărcare arată dacă aplicația dvs. va continua să funcționeze atunci când un număr mare de persoane încep să o folosească. Sunt similare cu testele canare și funcționale, dar nu sunt efectuate în medii locale sau de producție. De obicei, se folosește un mediu special de organizare, care este similar cu mediul de producție.

Este important să rețineți că aceste teste nu folosesc servicii reale de la terți, care ar putea fi nemulțumiți de testarea de încărcare externă a serviciilor lor de producție și, ca urmare, ar putea percepe suplimentar.

3. Mențineți tipurile de testare separate

Când vă concepeți planul de testare automată, fiecare tip de test ar trebui să fie separat, astfel încât să poată rula independent. Deși acest lucru necesită o organizare suplimentară, merită, deoarece amestecarea testelor poate crea probleme.

Aceste teste au diferite:

  • Intenții și concepte de bază (deci separarea lor creează un precedent bun pentru următoarea persoană care se uită la cod, inclusiv „viitorul tu”).
  • Timpi de execuție (deci rularea testelor unitare mai întâi permite un ciclu de testare mai rapid atunci când un test eșuează).
  • Dependențe (deci este mai eficient să încărcați doar cele necesare într-un tip de testare).
  • Infrastructuri necesare.
  • Limbaje de programare (în anumite cazuri).
  • Poziții în conducta de integrare continuă (CI) sau în afara acesteia.

Este important să rețineți că, cu majoritatea limbilor și stivelor de tehnologie, puteți grupa, de exemplu, toate testele unitare împreună cu subfolderele numite după module funcționale. Acest lucru este convenabil, reduce frecarea la crearea de module funcționale noi, este mai ușor pentru versiunile automate, are ca rezultat mai puțină dezordine și este o altă modalitate de a simplifica testarea.

4. Executați testele automat

Imaginați-vă o situație în care ați scris niște teste, dar după ce ați retras repo-ul câteva săptămâni mai târziu, observați că acele teste nu mai trec.

Acesta este un memento neplăcut că testele sunt cod și, ca orice altă bucată de cod, trebuie menținute. Cel mai bun moment pentru aceasta este chiar înainte de momentul în care crezi că ți-ai terminat munca și vrei să vezi dacă totul funcționează în continuare conform intenției. Aveți tot contextul necesar și puteți repara codul sau schimba testele eșuate mai ușor decât colegul dvs. care lucrează la un alt subsistem. Dar acest moment există doar în mintea ta, așa că cel mai comun mod de a rula teste este automat după un push către ramura de dezvoltare sau după crearea unei cereri de extragere.

În acest fel, ramura ta principală va fi întotdeauna într-o stare validă sau, cel puțin, vei avea o indicație clară a stării acesteia. O conductă automată de construire și testare – sau o conductă CI – ajută:

  • Asigurați-vă că codul poate fi construit.
  • Eliminați potențialele probleme „Funcționează pe mașina mea” .
  • Furnizați instrucțiuni rulabile despre cum să pregătiți un mediu de dezvoltare.

Configurarea acestui canal necesită timp, dar canalul poate dezvălui o serie de probleme înainte ca acestea să ajungă la utilizatori sau clienți, chiar și atunci când sunteți singurul dezvoltator.

Odată rulat, CI dezvăluie, de asemenea, noi probleme înainte ca acestea să aibă șansa de a crește în domeniu. Ca atare, prefer să-l configurez imediat după ce am scris primul test. Puteți găzdui codul într-un depozit privat pe GitHub și puteți configura GitHub Actions. Dacă repo-ul tău este public, ai chiar mai multe opțiuni decât GitHub Actions. De exemplu, planul meu automat de testare rulează pe AppVeyor, pentru un proiect cu o bază de date și trei tipuri de teste.

Prefer să-mi structurez pipeline pentru proiecte de producție după cum urmează:

  1. Compilare sau transpilare
  2. Teste unitare: sunt rapide și nu necesită dependențe
  3. Configurarea și inițializarea bazei de date sau a altor servicii
  4. Teste de integrare: au dependențe în afara codului dvs., dar sunt mai rapide decât testele funcționale
  5. Teste funcționale: când alți pași s-au finalizat cu succes, rulați întreaga aplicație

Nu există teste canare sau teste de sarcină. Din cauza specificului și cerințelor lor, acestea ar trebui să fie inițiate manual.

5. Scrieți numai testele necesare

Scrierea de teste unitare pentru tot codul este o strategie comună, dar uneori aceasta pierde timp și energie și nu vă oferă încredere. Dacă sunteți familiarizat cu conceptul de „piramidă de testare”, s-ar putea să credeți că tot codul dvs. trebuie acoperit cu teste unitare, cu doar un subset acoperit de alte teste de nivel superior.

Nu văd că este nevoie să scriu un test unitar care să asigure că mai multe dependențe batjocorite sunt apelate în ordinea dorită. Pentru a face asta necesită configurarea mai multor simulari și verificarea tuturor apelurilor, dar tot nu mi-ar da încrederea că modulul funcționează. De obicei, scriu doar un test de integrare care folosește dependențe reale și verifică doar rezultatul; asta îmi oferă o oarecare încredere că conducta din modulul testat funcționează corect.

În general, scriu teste care îmi fac viața mai ușoară în timp ce implementez funcționalitatea și o susțin mai târziu.

Pentru majoritatea aplicațiilor, urmărirea unei acoperiri de cod de 100% adaugă multă muncă obositoare și elimină bucuria de a lucra cu teste și programare în general. După cum spune Martin Fowler Test Coverage:

Acoperirea testului este un instrument util pentru a găsi părți netestate ale unei baze de cod. Acoperirea testelor este de puțin folos ca declarație numerică a cât de bune sunt testele dvs.

Astfel, vă recomand să instalați și să rulați analizorul de acoperire după ce ați scris câteva teste. Raportul cu linii de cod evidențiate vă va ajuta să înțelegeți mai bine căile de execuție și să găsiți locuri neacoperite care ar trebui acoperite. De asemenea, uitându-vă la getters, setters și fațade, veți vedea de ce o acoperire 100% nu este distractivă.

6. Joacă Lego

Din când în când, văd întrebări precum „Cum pot testa metodele private?” Tu nu. Dacă ați pus această întrebare, ceva a mers deja prost. De obicei, înseamnă că ați încălcat principiul responsabilității unice și modulul dvs. nu face ceva în mod corespunzător.

Refactorizați acest modul și trageți logica pe care o considerați importantă într-un modul separat. Nu există nicio problemă cu creșterea numărului de fișiere, ceea ce va duce la codul structurat ca cărămizi Lego: foarte lizibil, întreținut, înlocuibil și testabil.

În stânga este un teanc de dreptunghiuri. Cel mai de sus este etichetat OrderProcessor, iar unele dintre cele de sub el sunt etichetate Access Order Data, Price Check și Place Order. O săgeată indică din stiva din stânga spre dreapta, unde OrderProcessor este o cărămidă Lego lateral, cu cărămizi în diferite stadii de atașare și detașare de ea, inclusiv OrderDataProvider, PriceChecker și OrderPlacer.
Refactorizarea unui modul pentru a semăna cu cărămizile Lego.

Structurarea corectă a codului este mai ușor de spus decât de făcut. Iată două sugestii:

Programare functionala

Merită să învățați despre principiile și ideile programării funcționale. Majoritatea limbajelor principale, cum ar fi C, C++, C#, Java, Assembly, JavaScript și Python, vă obligă să scrieți programe pentru mașini. Programarea funcțională este mai potrivită pentru creierul uman.

Acest lucru poate părea contraintuitiv la început, dar luați în considerare acest lucru: un computer va fi bine dacă puneți tot codul într-o singură metodă, utilizați o bucată de memorie partajată pentru a stoca valori temporare și utilizați o cantitate suficientă de instrucțiuni de salt. Mai mult, compilatorii din etapa de optimizare fac uneori acest lucru. Cu toate acestea, creierul uman nu se ocupă cu ușurință de această abordare.

Programarea funcțională te obligă să scrii funcții pure fără efecte secundare, cu tipuri puternice, într-o manieră expresivă. În acest fel, este mult mai ușor să raționezi despre o funcție, deoarece singurul lucru pe care îl produce este valoarea ei de returnare. Episodul de podcast Programing Throwdown Programare funcțională cu Adam Gordon Bell vă va ajuta să obțineți o înțelegere de bază și puteți continua cu episoadele Corecursive Limbajul de programare al lui Philip Wadler și Teoria categoriilor cu Bartosz Milewski. Ultimele două mi-au îmbogățit foarte mult percepția despre programare.

Dezvoltare bazată pe teste

Recomand stăpânirea TDD. Cel mai bun mod de a învăța este să exersezi. String Calculator Kata este o modalitate excelentă de a exersa cu codul kata. Stăpânirea kata va dura timp, dar în cele din urmă vă va permite să absorbiți pe deplin ideea de TDD, ceea ce vă va ajuta să creați un cod bine structurat, cu care să lucrați cu plăcere și, de asemenea, testabil.

O notă de precauție: uneori, veți vedea puriștii TDD susținând că TDD este singura modalitate corectă de programare. În opinia mea, este pur și simplu un alt instrument util în cutia ta de instrumente, nimic mai mult.

Uneori, trebuie să vedeți cum să ajustați modulele și procesele unul în raport cu celălalt și nu știți ce date și semnături să utilizați. În astfel de cazuri, scrieți codul până când se compilează, apoi scrieți teste pentru a depana și a depana funcționalitatea.

În alte cazuri, cunoașteți intrarea și ieșirea pe care o doriți, dar nu aveți idee cum să scrieți corect implementarea din cauza logicii complicate. Pentru aceste cazuri, este mai ușor să începeți să urmați procedura TDD și să vă construiți codul pas cu pas, mai degrabă decât să vă gândiți la implementarea perfectă.

7. Păstrați testele simple și concentrate

Este o plăcere să lucrezi într-un mediu de cod bine organizat, fără distrageri inutile. De aceea, este important să aplicați principiile SOLID, KISS și DRY la teste – utilizând refactorizarea atunci când este nevoie.

Uneori aud comentarii de genul: „Urăsc să lucrez într-o bază de cod puternic testată, deoarece fiecare schimbare îmi cere să repar zeci de teste”. Aceasta este o problemă de întreținere ridicată cauzată de teste care nu sunt concentrate și încearcă să testeze prea mult. Principiul „Fă bine un lucru” se aplică și testelor: „Testează bine un lucru”; fiecare test ar trebui să fie relativ scurt și să testeze un singur concept. „Testează bine un lucru” nu înseamnă că ar trebui să fii limitat la o singură afirmație per test: poți folosi zeci dacă testezi cartografierea datelor non-triviale și importante.

Această atenție nu se limitează la un singur test sau tip de test specific. Imaginați-vă că vă ocupați de o logică complicată pe care ați testat-o ​​folosind teste unitare, cum ar fi maparea datelor din sistemul ERP la structura dvs. și aveți un test de integrare care accesează simulate API-uri ERP și returnează rezultatul. În acest caz, este important să vă amintiți ce acoperă deja testul unitar, astfel încât să nu testați din nou maparea în testele de integrare. De obicei, este suficient să vă asigurați că rezultatul are câmpul de identificare corect.

Cu codul structurat precum cărămizi Lego și teste concentrate, modificările logicii de afaceri nu ar trebui să fie dureroase. Dacă modificările sunt radicale, pur și simplu aruncați fișierul și testele aferente acestuia și faceți o nouă implementare cu noi teste. În cazul unor modificări minore, de obicei modificați unul până la trei teste pentru a îndeplini noile cerințe și a face modificări logicii. Este bine să schimbi testele; te poți gândi la această practică ca la o contabilitate în partidă dublă.

Alte modalități de a obține simplitate includ:

  • Crearea de convenții pentru structurarea fișierelor de testare, structurarea conținutului de testare (de obicei o structură Aranjare-Act-Asertare) și denumirea testului; apoi, cel mai important, respectarea acestor reguli în mod consecvent.
  • Extragerea de blocuri mari de cod în metode precum „pregătirea cererii” și crearea de funcții de ajutor pentru acțiuni repetate.
  • Aplicarea modelului de constructor pentru configurarea datelor de testare.
  • Folosind (în testele de integrare) același container DI pe care îl utilizați în aplicația principală, astfel încât fiecare instanțiere va fi la fel de banală ca TestServices.Get() fără a crea manual dependențe. În acest fel, va fi ușor de citit, întreținut și scris teste noi, deoarece aveți deja ajutoare utili.

Dacă simțiți că un test devine prea complicat, pur și simplu opriți-vă și gândiți-vă. Fie modulul, fie testul dvs. trebuie refactorizat.

8. Folosiți instrumente pentru a vă ușura viața

Te vei confrunta cu multe sarcini obositoare în timpul testării. De exemplu, configurarea mediilor de testare sau a obiectelor de date, configurarea stub-urilor și a modelelor pentru dependențe și așa mai departe. Din fericire, fiecare stivă tehnologică matură conține mai multe instrumente pentru a face aceste sarcini mult mai puțin obositoare.

Vă sugerez să scrieți prima sută de teste, dacă nu ați făcut-o deja, apoi să investiți ceva timp pentru a identifica sarcini repetitive și pentru a afla despre instrumentele legate de testare pentru stiva dvs. de tehnologie.

Pentru inspirație, iată câteva instrumente pe care le puteți folosi:

  • alergători de testare. Căutați sintaxă concisă și ușurință în utilizare. Din experiența mea, pentru .NET, recomand xUnit (deși NUnit este și o alegere solidă). Pentru JavaScript sau TypeScript, merg cu Jest. Încercați să găsiți cea mai bună potrivire pentru sarcinile și mentalitatea dvs., deoarece instrumentele și provocările evoluează.
  • Batjocorirea bibliotecilor . Pot exista simulari de nivel scăzut pentru dependențele de cod, cum ar fi interfețele, dar există și simulari de nivel superior pentru API-urile web sau bazele de date. Pentru JavaScript și TypeScript, falsurile de nivel scăzut incluse în Jest sunt OK. Pentru .NET. Folosesc Moq, deși NSubstitute este grozav. În ceea ce privește mockurile API-ului web, îmi place să folosesc WireMock.NET. Poate fi folosit în locul unui API pentru a depana și a depana gestionarea răspunsurilor. De asemenea, este foarte fiabil și rapid în testele automate. Bazele de date ar putea fi batjocorite folosind omologii lor din memorie. EfCore în .NET oferă o astfel de opțiune.
  • Biblioteci de generare de date . Aceste utilitare umplu obiectele de date cu date aleatorii. Sunt utile atunci când, de exemplu, vă pasă doar de câteva câmpuri dintr-un obiect mare de transfer de date (dacă asta; poate doriți doar să testați corectitudinea mapării). Le puteți folosi pentru teste și, de asemenea, ca date aleatorii pentru a fi afișate într-un formular sau pentru a vă completa baza de date. În scopuri de testare, folosesc AutoFixture în .NET.
  • Biblioteci de automatizare a UI . Aceștia sunt utilizatori automatizați pentru teste automate: aceștia pot rula aplicația dvs., pot completa formulare, pot face clic pe butoane, pot citi etichete și așa mai departe. Pentru a naviga prin toate elementele aplicației dvs., nu trebuie să vă ocupați de clicurile după coordonate sau recunoașterea imaginii; platformele majore au instrumentele necesare pentru a găsi elementele necesare după tip, identificator sau date, astfel încât să nu fie nevoie să vă schimbați testele la fiecare reproiectare. Sunt robuste, așa că odată ce le-ați făcut să funcționeze pentru dvs. și CI (uneori aflați că lucrurile funcționează numai pe mașina dvs. ), vor continua să funcționeze. Îmi place să folosesc FlaUI pentru .NET și Cypress pentru JavaScript și TypeScript.
  • Biblioteci de afirmații . Majoritatea testelor includ instrumente de afirmare, dar există cazuri în care un instrument independent vă poate ajuta să scrieți aserțiuni complexe folosind o sintaxă mai curată și mai lizibilă, cum ar fi Fluent Assertions pentru .NET. Îmi place în special funcția de a afirma că colecțiile sunt egale, indiferent de ordinea unui articol sau adresa sa în memorie.

Fie ca fluxul să fie cu tine

Fericirea este strâns cuplată cu așa-numita experiență „flux” descrisă în detaliu în cartea Flow: The Psychology of Optimal Experience . Pentru a realiza această experiență de flux, trebuie să fii implicat într-o activitate cu un set clar de obiective și să fii capabil să-ți vezi progresul. Sarcinile ar trebui să aibă ca rezultat feedback imediat, pentru care testele automate sunt ideale. De asemenea, trebuie să găsiți un echilibru între provocări și abilități, care depinde de fiecare individ. Testele, în special atunci când sunt abordate cu TDD, vă pot ajuta să vă ghidați și să vă insufleți încredere. Ele vă ajută să vă stabiliți obiective specifice, fiecare test trecut fiind un indicator al progresului dvs.

Abordarea corectă a testării te poate face mai fericit și mai productiv, iar testele scad șansele de epuizare. Cheia este să vedeți testarea ca un instrument (sau un set de instrumente) care vă poate ajuta în rutina de dezvoltare zilnică, nu ca un pas împovărător pentru a vă proteja codul în viitor.

Testarea este o parte necesară a programării, care permite inginerilor software să îmbunătățească modul în care lucrează, să ofere cele mai bune rezultate și să-și folosească timpul în mod optim. Poate și mai important, testele pot ajuta dezvoltatorii să se bucure mai mult de munca lor, crescându-le astfel moralul și motivația.