Unitatea de testare succint Strategii pentru teste unitare

Abordările de testare depind de locul în care vă aflați în proiect și de "bugetul" dvs., în ceea ce privește timpul, banii, forța de muncă, nevoia etc. În mod ideal, testarea unităților este inclusă în procesul de dezvoltare, dar realist întâlnim adesea programe existente sau moștenite care au o acoperire insuficientă sau fără cod, dar trebuie modernizate sau întreținute. 

Cel mai grav scenariu este un produs care este în prezent în curs de dezvoltare, dar prezintă un număr crescut de defecțiuni în timpul dezvoltării sale, din nou cu o acoperire insuficientă sau fără cod. În calitate de manager de produs, fie la începutul unui efort de dezvoltare, fie ca urmare a predării unei aplicații existente, este important să se dezvolte o strategie rezonabilă de testare a unităților. 

Amintiți-vă că testele unității ar trebui să ofere beneficii măsurabile proiectului dvs. pentru a compensa răspunderea dezvoltării, întreținerii și testării proprii. În plus, strategia pe care o adoptați pentru testarea unității poate afecta arhitectura aplicației dvs. În timp ce acest lucru este aproape întotdeauna un lucru bun, acesta poate introduce cheltuieli inutile pentru nevoile dvs..

Pornind de la cerințe

Dacă începeți o aplicație suficient de complexă dintr-o artă curată și tot ce este în mâinile dvs. este un set de cerințe, luați în considerare următoarele îndrumări.

Prioritizarea cerințelor de calcul

Prioritizați cerințele de calcul ale aplicației pentru a determina unde este complexitatea. Complexitatea poate fi determinată prin descoperirea numărului de state pe care o anumită computare trebuie să le găzduiască sau poate fi rezultatul unui set mare de date de intrare necesare pentru efectuarea calculului sau poate fi pur și simplu complex de algoritm, pe inelul de redundanță al unui satelit. De asemenea, ia în considerare în cazul în care codul este probabil să se schimbe în viitor, ca urmare a unor cerințe în schimbare necunoscută. În timp ce sună ca și cum ar fi nevoie de clarviziune, un arhitect calificat de software poate clasifica codul în scop general (rezolvarea unei probleme obișnuite) și domeniul specific (rezolvând o problemă specifică a cerințelor). Acesta din urmă devine un candidat pentru schimbarea viitoare.

În timp ce testarea unităților de scriere a funcțiilor triviale este ușoară, rapidă și plăcută în numărul de cazuri de testare pe care le trece programul, acestea sunt cele mai puțin eficiente teste de cost - au nevoie de timp pentru a scrie și, cel mai probabil, vor fi corect scrise pentru a începe cu și cel mai probabil nu se va schimba în timp, acestea sunt cele mai puțin utile, deoarece baza de cod a aplicației crește. În schimb, concentrați-vă strategia de testare a unităților pe codul care este specific domeniului și complex.

Selectați o arhitectură

Unul dintre avantajele lansării unui proiect dintr-un set de cerințe este că vă puteți crea arhitectura (sau selectați o arhitectură terță parte) ca parte a procesului de dezvoltare. Cadrele terță parte care vă permit să utilizați arhitecturi cum ar fi inversarea controlului (și conceptul conex al injecției de dependență), precum și arhitecturile formale cum ar fi Model-View-Controller (MVC) și Model-View-ViewModel (MVVM) unitate de testare pentru simplul motiv că o arhitectură modulară este, de obicei, mai ușor de test unitate. Aceste arhitecturi se separă:

  • Prezentarea (vizualizarea).
  • Modelul (responsabil pentru persistență și reprezentare a datelor).
  • Controlorul (unde ar trebui să apară calculele).

În timp ce unele aspecte ale modelului ar putea fi candidați pentru testarea unităților, majoritatea testelor unitare vor fi probabil scrise față de metodele din controler sau din modelul de vizualizare, unde sunt implementate calculele pe model sau vizualizare.

Faza de întreținere

Testarea unităților poate fi benefică chiar dacă sunteți implicat în întreținerea unei aplicații, una care necesită adăugarea de noi caracteristici unei aplicații existente sau pur și simplu remedierea unor erori ale unei aplicații moștenite. Există mai multe abordări pe care le poate lua o aplicație existentă și întrebări care stau la baza acestor abordări care pot determina rentabilitatea testării unităților:

  • Încercați să scrieți teste unitare numai pentru funcții noi și corecții de erori? Este o caracteristică sau un bug care stabilește ceva care va beneficia de testarea de regresie sau este o problemă izolată o singură dată, care este mai ușor testată în timpul testelor de integrare?
  • Începeți să scrieți testele unității împotriva caracteristicilor existente? În caz afirmativ, cum să stabiliți prioritățile care ar trebui să fie testate mai întâi?
  • Baza de cod existent funcționează bine cu testarea unității sau codul necesită mai întâi refactorizarea pentru a izola unitățile de cod?
  • Ce setări sau teardowns sunt necesare pentru caracteristica sau testarea de bug-uri?
  • Ce dependențe pot fi descoperite cu privire la modificările de cod care pot duce la efecte secundare în alt cod și dacă testele unității ar trebui lărgite pentru a testa comportamentul codului dependent?

A intrat în faza de întreținere a unei aplicații moștenite care nu are testări pe unitate nu este banală - planificarea, examinarea și investigarea codului pot necesita adesea mai multe resurse decât reparația pur și simplu a bug-ului. Cu toate acestea, utilizarea judicioasă a testării unităților poate fi eficientă din punct de vedere al costurilor și, deși acest lucru nu este întotdeauna ușor de stabilit, merită exercițiul, dacă nu pentru alt motiv decât pentru a înțelege mai bine baza de cod.


Determinați procesul dvs.

Există trei strategii pe care le puteți lua cu privire la procesul de testare a unității: "Dezvoltare condusă de test", "Codul întâi" și, deși poate părea antithetică temei acestei cărți, procesul "No Unit Test".

Dezvoltare bazată pe dezvoltare

Un tabără este "Dezvoltare condusă de test", rezumată de următorul flux de lucru:

Având în vedere o cerință de calcul (a se vedea secțiunea anterioară), mai întâi, scrieți un stub pentru metodă.

  • Dacă sunt necesare dependențe de alte obiecte care nu sunt încă implementate (obiecte transmise ca metode sau returnate de metodă), implementați-le ca interfețe goale.
  • Dacă lipsesc proprietăți, puneți-o în aplicare pentru proprietăți care sunt necesare pentru a verifica rezultatele.
  • Scrieți orice cerințe de încercare pentru configurare sau teardon.
  • Scrie testele. Motivele pentru scrierea oricăror pietre inainte de scrierea testului sunt: ​​în primul rând, să profite de IntelliSense la scrierea testului; în al doilea rând, pentru a stabili că codul încă se compilează; și în al treilea rând, pentru a se asigura că metoda testată, parametrii, interfețele și proprietățile acesteia s-au sincronizat cu privire la denumire.
  • Executați testele, verificând că acestea nu reușesc.
  • Codificați punerea în aplicare.
  • Rulați testele, verificând dacă reușesc.

În practică, acest lucru este mai greu decât pare. Este ușor să cadă prada de a scrie teste care nu sunt eficiente din punct de vedere al costurilor și adesea se descoperă că metoda testată nu este o unitate suficient de fină pentru a fi un bun candidat pentru un test. Poate că metoda face prea mult, necesită prea multă configurare sau teardon sau are dependențe de prea multe alte obiecte care trebuie inițializate la o stare cunoscută. Acestea sunt toate lucrurile care sunt mai ușor de descoperit când scrieți codul, nu testul.

Unul dintre avantajele unei abordări bazate pe teste este că procesul instilujează disciplina de testare a unității și scrierea mai întâi a testelor unității. Este ușor să determinați dacă dezvoltatorul urmărește procesul. Cu practica, se poate face ușor de a face, de asemenea, procesul de cost-eficiente.

Un alt avantaj al unei abordări bazate pe încercări este că, prin natura sa, ea impune un fel de arhitectură. Ar fi absurd, dar posibil să scrie un test de unitate care inițiază un formular, pune valorile într-un control și apoi apelează o metodă care se așteaptă să efectueze unele calcule pe valori, cum ar fi nevoie de acest cod (de fapt găsit aici):

void privat btnCalculate_Click (expeditor obiect, System.EventArgs e) double Principal, AnnualRate, InterestEarned; dublu FutureValue, RatePerPeriod; int NumberOfPeriods, CompoundType; Principal = Double.Parse (txtPrincipal.Text); AnnualRate = Double.Parse (txtInterest.Text) / 100; dacă (rdoMonthly.Checked) CompoundType = 12; altfel dacă (rdoQuarterly.Checked) CompoundType = 4; altfel dacă (rdoSemiannually.Checked) CompoundType = 2; altfel CompusType = 1; NumberOfPeriods = Int32.Parse (txtPeriods.Text); dublu i = AnnualRate / CompoundType; int n = CompusType * NumberOfPeriods; RatePerPeriod = Rata anuală / Număr de perioade; FutureValue = Principal * Math.Pow (1 + i, n); InterestEarned = FutureValue - Principal; txtInterestEarned.Text = InteresEarned.ToString ("C"); txtAmountEarned.Text = FutureValue.ToString ("C"); 

Codul precedent este netestabil deoarece este încurcat cu dispozitivul de tratare a evenimentelor și interfața cu utilizatorul. Mai degrabă, s-ar putea scrie metoda calculului dobânzii compuse:

public enum CompoundType (Anual = 1, SemiAnnual = 2, Trimestrial = 4, Lunar = 12) Private DoubleCompleteCalculație dublă (dublu principal, dublu anualRate, compusTip compusTip, int perioade) double annualRateDecimal = annualRate / 100.0; dublu i = annualRateDecimal / (int) compoundType; int n = (int) compoundType * perioade; rate dublePerPeriod = annualRateDecimal / perioade; dublu viitorValue = principal * Math.Pow (1 + i, n); dobândă dublăEaned = futureValue - principal; returnează dobândăEaned; 

ceea ce ar permite ca un test simplu să fie scris:

[TestMethod] public void CompoundInterestTest () dobândă dublă = CompuseInterestCalculation (2500, 7.55, CompoundType.Monthly, 4); Assert.AreEqual (878,21, dobândă, 0,01); 

Mai mult, prin utilizarea testelor parametrizate, ar fi ușor să se testeze fiecare tip de compus, un interval de ani, și dobânzi și valori principale diferite.

Abordarea bazată pe test, de fapt facilitează un proces de dezvoltare mai formalizat prin descoperirea unităților testabile reale și izolarea lor de dependențele de trecere a frontierei.

Primul cod, testați al doilea

Mai întâi codarea este mai naturală, doar pentru că este modul obișnuit în care sunt dezvoltate aplicațiile. Cerința și implementarea ei pot părea, de asemenea, destul de ușor la prima vedere, astfel încât scrierea mai multor teste unitare pare să fie o utilizare proastă a timpului. Alți factori, cum ar fi termenele limită, pot forța un proiect într-un proces de elaborare "primiți doar codul astfel încât să putem expedia".

Problema cu abordarea cod-prima este că este ușor de scris cod care necesită tipul de test pe care l-am văzut mai devreme. Codul necesită mai întâi o disciplină activă pentru a testa codul care a fost scris. Această disciplină este incredibil de dificil de realizat, mai ales că există întotdeauna următoarea caracteristică nouă de implementat.

De asemenea, este nevoie de inteligență, dacă doriți, pentru a evita scrierea codului încrucișat, de trecere a frontierei și a disciplinei pentru a face acest lucru. Cine nu a făcut clic pe un buton în designerul Visual Studio și a codificat calculul evenimentului acolo în stubul pe care Visual Studio îl creează pentru dvs.? Este ușor și pentru că instrumentul vă direcționează în această direcție, programatorul naiv va crede că acesta este cel mai bun mod de a codifica.

Această abordare necesită o analiză atentă a competențelor și disciplinei echipei dvs. și necesită o monitorizare mai atentă a echipei, în special în perioadele de stres ridicat, când abordările disciplinate tind să se descompună. Acordat, o disciplină condusă de test poate fi, de asemenea, aruncată ca termen limită, dar aceasta tinde să fie o decizie conștientă de a face o excepție, în timp ce ea poate deveni cu ușurință regula într-o primă abordare a codului.

Nu există teste de unitate

Doar pentru că nu aveți teste unitare nu înseamnă că eliminați testul. S-ar putea fi pur și simplu faptul că testarea pune accentul pe procedurile de testare de acceptare sau pe testarea integrării.

Strategii de testare a echilibrării

Un proces de testare unitară eficient din punct de vedere al costurilor necesită un echilibru între strategiile de dezvoltare bazate pe încercări, codul întâi, testul secundar și strategia "Test some other way". Eficiența costurilor pentru testarea unităților ar trebui să fie întotdeauna luată în considerare, precum și factori precum experiența dezvoltatorilor din echipă. În calitate de manager, este posibil să nu doriți să auziți că o abordare bazată pe încercări este o idee bună dacă echipa dvs. este destul de verde și aveți nevoie de procesul de a insufla disciplina și abordarea.

Cod