Acesta este un extras din Cartea electronică de testare a unității, de Marc Clifton, oferită cu amabilitate de Syncfusion.
Articolele anterioare au atins o varietate de preocupări și beneficii ale testării unităților. Acest articol reprezintă o analiză mai formalizată a costurilor și beneficiilor testării unităților.
Codul de testare al unității dvs. este o entitate separată de codul testat, dar are în comun multe dintre aceleași probleme solicitate de codul dvs. de producție:
În plus, testele unitare pot de asemenea:
Atunci când se stabilește dacă testele pot fi scrise pe o singură metodă, trebuie să țineți cont de:
Trebuie să ne dăm seama că numărul de linii de cod pentru a testa chiar și o metodă simplă ar putea fi considerabil mai mare decât numărul de linii al metodei în sine.
Schimbarea codului de producție poate invalida adesea testele unității. Modificările de cod se împart în două categorii:
Fosta persoană deține, de obicei, cerințe de întreținere mici sau lipsite de teste existente pe unitățile existente. Acestea din urmă necesită deseori o reparație considerabilă a testelor unitare, în funcție de complexitatea schimbării:
Așa cum am menționat anterior, testarea unităților, în special într-un proces bazat pe teste, impune anumite arhitecturi minime și paradigme de implementare. Pentru a sprijini în continuare ușurința înființării sau ruperii unor zone ale codului, testarea unităților poate beneficia, de asemenea, de considerente de arhitectură mai complexe, cum ar fi inversarea controlului.
La minim, majoritatea claselor ar trebui să faciliteze baterea oricărui obiect. Acest lucru poate îmbunătăți semnificativ performanța testelor - de exemplu, testarea unei metode care efectuează verificări de integritate a cheilor externe (mai degrabă decât bazându-se pe baza de date pentru a raporta erorile ulterior) nu ar trebui să necesite o configurare complexă sau o teardonare a scenariului de testare din baza de date în sine. Mai mult, nu ar trebui să solicite ca metoda să interogheze efectiv baza de date. Acestea sunt toate rezultate de performanță la test și se adaugă dependențe de o conexiune live, autentificată cu baza de date și, prin urmare, nu se pot ocupa de o altă stație de lucru care rulează exact același test în același timp. În schimb, prin batjocurarea conexiunii bazei de date, testul unității poate seta cu ușurință scenariul din memorie și poate trece obiectul de conexiune ca interfață.
Cu toate acestea, simpla batjocură a unei clase nu este neapărat cea mai bună practică - ar fi mai bine să refaceți codul, astfel încât toate informațiile necesare metodei să fie obținute separat, separând achiziția datelor de la calculul datelor. Acum, calculul poate fi efectuat fără a bate obiectul care este responsabil pentru achiziționarea datelor, ceea ce simplifică și mai mult configurația de testare.
Există câteva strategii de atenuare a costurilor care ar trebui luate în considerare.
Cea mai eficientă metodă de reducere a costului testării unităților este evitarea necesității de a scrie testul. În timp ce acest lucru pare evident, cum se realizează acest lucru? Răspunsul este să se asigure că datele transmise metodei sunt corecte - cu alte cuvinte - intrarea corectă, ieșirea corectă (contravaloarea "gunoi, gunoi"). Da, probabil că totuși doriți să testați calculele însăși, dar dacă puteți garanta că contractul este îndeplinit de către apelant, nu este necesar să testați metoda pentru a vedea dacă se ocupă de parametrii incorecți (încălcări ale contractului).
Acesta este un pic de panta alunecoasa pentru ca nu aveti nicio idee despre modul in care metoda ar putea fi numita in viitor - de fapt, s-ar putea ca metoda sa continue sa valideze contractul sau, dar in contextul în care este utilizat în prezent, dacă puteți garanta că contractul este întotdeauna îndeplinit, atunci nu există nici un punct real în scris teste împotriva contractului.
Cum asigurăți intrările corecte? Pentru valorile care provin dintr-o interfață de utilizator, filtrarea și controlul adecvat al interacțiunii utilizatorului pentru a pre-filtra valorile reprezintă o abordare. O abordare mai sofisticată este aceea de a defini mai degrabă tipuri specializate decât de a se baza pe tipuri de scopuri generale. Luați în considerare metoda Divide descrisă mai devreme:
public static int Divide (int numitor, int numitor) if (denominator == 0) arunca noua ArgumentOutOfRangeException ("Denumirea nu poate fi 0."); numarator / numitor retur;
Dacă numitorul a fost un tip specializat care garanta o valoare diferită de zero:
clasa publica NonZeroDouble protected int val; public int Valoare get return val; set if (value == 0) arunca noua ArgumentOutOfRangeException ("Valoarea nu poate fi 0."); val = value;
metoda Divide nu va trebui niciodată testată pentru acest caz:
////// Un exemplu de utilizare a specificității de tip pentru a evita un test de contract. /// public static int Divide (numărător int, numitor NonZeroDouble) numărătorul retur / denominator.Value;
Atunci când se consideră că acest lucru îmbunătățește specificitatea tipului aplicației și stabilește tipuri (reușite) reutilizabile, se înțelege modul în care acest lucru evită necesitatea de a scrie o serie de teste unitare, deoarece codul utilizează adesea tipuri prea generale.
Întrebați-vă - metoda mea ar trebui să fie responsabilă de tratarea excepțiilor de la terți, cum ar fi serviciile web, bazele de date, conexiunile la rețea etc.? Se poate argumenta că răspunsul este "nu". Acordat, acest lucru necesită o serie de activități suplimentare - API terț (sau chiar cadru) are nevoie de un pachet care să gestioneze excepția și o arhitectură în care starea internă a aplicația poate fi derulată înapoi atunci când survine o excepție și ar trebui probabil implementată. Cu toate acestea, acestea sunt, probabil, îmbunătățiri valoroase pentru aplicație oricum.
Exemplele anterioare - intrările corecte, sistemele de tip specializat, evitând excepțiile unor terțe părți - toate împing această problemă la un scop mai general și eventual un cod reutilizabil. Acest lucru ajută la evitarea procesului de validare a contractelor similare sau similare, a testelor privind unitățile de tratare a excepțiilor și vă permite să vă concentrați în schimb pe teste care validează ce ar trebui să facă metoda în condiții normale,.
După cum sa menționat anterior, există avantaje clare în ceea ce privește costurile pentru testarea unităților.
Unul dintre avantajele evidente este procesul de formalizare a cerințelor codului intern de la cerințele externe de utilizare / proces. Pe măsură ce trece prin acest exercițiu, direcția cu arhitectura generală este de obicei un beneficiu lateral. Mai concret, dezvoltarea unei suite de teste care îndeplinesc o cerință specifică din unitate perspectivă (mai degrabă decât test de integrare perspectivă) este dovada obiectivă a faptului că acest cod implementează cerința.
Testarea prin regresie este un alt beneficiu (de multe ori măsurabil). Odată cu creșterea codului de bază, verificarea faptului că codul existent funcționează în continuare așa cum se intenționează economisește timp considerabil de testare manuală și evită scenariul "Oops, nu am testat pentru acest". Mai mult, atunci când se semnalează o eroare, aceasta poate fi corectată imediat, adesea salvându-i pe ceilalți membri ai echipei durerea de cap considerabilă, întrebându-se de ce ceva pe care se bazau brusc nu funcționează corect.
Testele de unitate verifică nu doar faptul că o metodă se mânează corect atunci când sunt date intrări necorespunzătoare sau excepții ale unor terțe părți (după cum sa descris mai devreme, încercați să reduceți aceste tipuri de teste), dar și modul în care metoda se așteaptă să se comporte în condiții normale. Aceasta furnizează documentația valoroasă dezvoltatorilor, în special membrilor noii echipe - prin testul unității pot cu ușurință să culeagă cerințele de instalare și cazurile de utilizare. Dacă proiectul dvs. suferă o refactorizare arhitecturală semnificativă, noile teste de unitate pot fi folosite pentru a ghida dezvoltatorii în redactarea codului lor dependent.
Așa cum am descris mai devreme, o arhitectură mai robustă prin utilizarea interfețelor, inversarea controlului, tipuri specializate etc. - toate acestea facilitează testarea unităților-de asemenea să îmbunătățească robustețea aplicației. Cerințele se modifică, chiar și în timpul dezvoltării, și o arhitectură bine gândită poate face față acestor modificări mult mai bine decât o aplicație care nu are sau are o mică importanță arhitecturală.
Mai degrabă decât să predați unui programator junior o cerință de nivel înalt care să fie pusă în aplicare la nivelul de calificare al programatorului, puteți garanta un nivel mai ridicat de cod și succes (și de a oferi o experiență de predare) prin faptul că aveți programatorul junior cod împotriva testului mai degrabă decât cerința. Acest lucru elimină o mulțime de practici incorecte sau presupuneri că un programator de juniori se termină cu implementarea (am fost toți acolo) și reduc reprelucrarea pe care un dezvoltator mai senior trebuie să o facă în viitor.
Există mai multe tipuri de recenzii de cod. Testele unității pot reduce timpul petrecut în revizuirea codului pentru problemele arhitecturale, deoarece acestea tind să forțeze arhitectura. În plus, testele unității validează calculul și pot fi, de asemenea, folosite pentru a valida toate căile de cod pentru o metodă dată. Acest lucru face că revizuirile codului sunt aproape inutile - testul unității devine o revizuire de sine a codului.
Un efect secundar interesant al conversiei cerințelor de utilizare externă sau de proces la testele formalizate de cod (și arhitectura lor de susținere) este aceea că:
Aceste descoperiri, ca urmare a procesului de testare a unității, identifică problemele anterioare ale procesului de dezvoltare, care, de obicei, ajută la reducerea confuziei, reprelucrarea și, prin urmare, reduc costurile.