Administrativ
Laboratoare
Tema
Teste
Resurse utile
Alte resurse
Arhiva Teme
Administrativ
Laboratoare
Tema
Teste
Resurse utile
Alte resurse
Arhiva Teme
Responsabil: Daniel Ciocîrlan Deadline: 10.01.2016 23:55 12.01.2016 23:55
Data publicării: 14.12.2015 Data tester-ului: 7.1.2016
La laborator am lucrat puțin cu framework-ul JUnit și am observat utilitatea testelor unitare când comparam rezultatele întoarse de către metode (sau efectele laterale) cu cele așteptate de noi. Totuși, în proiecte reale, aplicațiile Java sunt mult mai complexe decât ce facem la laborator. Testele unitare presupun izolarea componentelor de restul aplicației, de aceea se cheamă unitare, iar cele care care permit legături între module se numesc teste de integrare.
Spre exemplu, o metodă relativ simplă poate chema un modul extern complex (e.g. modul specializat de procesare intensă de date, un server sau o bază de date) și apoi să efectueze operații comparativ triviale. Când testăm o astfel de metodă, nu ne interesează datele reale întoarse de baza de date, serverul sau procesorul de date, ci doar corectitudinea operațiilor metodei noastre și atât.
De aceea, deseori se folosesc framework-uri de mocking pentru unit tests, care maschează operațiile complexe externe și întorc rezultate ca și când modulele externe funcționează corect și instantaneu.
Vom implementa o variantă ultra-simplificată a framework-ului Mockito. Pentru că Mockito folosește mult compiler black magic și reflection, în temă vom avea ceva simplificat și vom lucra cu interfața Mockable
.
interface Mockable<A, B> { Unit execute(); B execute(A arg); }
Mockable
este o interfață care expune două tipuri de calcul - unul care conceptual nu întoarce nimic (semnătura pare să ne contrazică, dar explicăm imediat), și unul care primește un argument și întoarce un rezultat.
Presupunem lumea noastră ca fiind limitată și în ea lucrăm doar cu obiecte Mockable.
Fie dat un obiect Mockable
:
Mockable<Integer, String> realObject = ...
Vrem să facem un mock-object pentru instanța noastră realObject
, astfel:
Mockable<Integer, String> mock = Mockito.mock(realObject);
Poate nimic special până aici, dar acum noul nostru Mockable e înzestrat cu extra puteri. Vrem ca atunci când obiectului mock
i se apelează, de exemplu, metoda execute
cu parametrul 2, să întoarcă un rezultat dat de noi. “Sintaxa” va fi următoarea.
Brace yourselves.
Mockito.watch().when(mock.execute(2)).thenReturn("3").andBeDoneWithIt();
Mai citiți o dată cu atenție linia de mai sus.
Acum ignorați pentru moment părțile cu watch()
și andBeDoneWithIt()
.
Urmăriți construcția .when(mock.execute(2)).thenReturn(“3”)
. Vrem ca atunci când pe obiectul mock se apelează metoda execute
cu parametrul 2, vom întoarce String-ul “3”. Nu ne interesează ce ar fi întors în mod real instanța realObject
de mai devreme. Astfel că atunci când scriem
System.out.println(mock.execute(2));
se va afișa la consolă, evident, String-ul “3”.
Mai mult decât atât, vrem să putem arunca excepții, la cerere, atunci când se apelează una dintre metode cu un anumit parametru. Exemple:
Mockito.watch().when(mock.execute()) .thenThrow(new RuntimeException("hello world")).andBeDoneWithIt(). Mockito.watch().when(mock.execute(2)) .thenThrow(new RuntimeException("not 2 again!")).andBeDoneWithIt().
De asemenea, vrem să controlăm comportamentul mock-urilor specificând ordinea rezultatelor întoarse sau a excepțiilor aruncate.
Brace for syntax.
Mockito.watch().when(mock.execute(3)) .thenThrow(new RuntimeException("hello world")) .thenReturn("4") .thenReturn("1") .thenThrow(new MySuperException()) // MySuperException e doar un exemplu .andBeDoneWithIt();
Când rulăm următorul cod observați ce se va întâmpla.
try{ mock.execute(3); } catch (Exception e) { System.out.println(e.getMessage()); // "hello world" } System.out.println(mock.execute(3)); // "4" System.out.println(mock.execute(3)); // "1" System.out.println(mock.execute(3)); // MySuperException neprinsă
Observați că “sintaxa” construcțiilor înlănțuite seamănă cu limbajul natural - “Mockito, watch when mock calls execute(2) then return “3”
then throw someException and be done with it”.
Veți avea probabil nevoie de structuri auxiliare. Părțile cu watch()
și andBeDoneWithIt()
sunt puncte în care vă puteți să executați cod special. Depinde, totuși - dacă vă structurați altfel codul, atunci puteți lăsa metodele goale. Trebuie totuși să permiteți construcțiile cu tot cu watch
și andBeDoneWithIt
- ele nu sunt opționale și vor face parte din orice watch.
Uitați-vă acum la semnătura metodei execute()
din interfața Mockable
. Încercați să intuiți de ce avem nevoie de Unit
. Ce ne facem dacă vrem să monitorizăm apelul mock.execute()
dacă execute()
ar fi întors void
? Cum am fi scris ….when(mock.execute()).then…
?
Astfel, metoda execute
, conceptual, nu întoarce nimic. Recomandăm să folosiți un design inteligent pentru a nu vă trezi cu multe Unit
-uri întoarse la fiecare nou apel.
Vedem deja cum aceste mocked objects sunt importante. Imaginați-vă că fiecare obiect real Mockable
interoghează 100 de servere. Acum cu mock-uri, vom întoarce rezultate după cum dorim, fără interacțiuni cu module “grele”, externe.
În testarea aplicațiilor se folosește deseori o metrică ce poartă numele de code coverage. Spus simplu, se măsoară cât la sută din codul scris a fost testat.
Pentru asta avem nevoie să instrumentăm apelurile metodelor - cu alte cuvinte, să monitorizăm când se apelează. Vom adăuga deci framework-ului nostru capacități de testare a code-flow-ului. Exemplu:
Mockito.verify(mock, Mockito.atLeastOnce()).execute(2);
Cu alte cuvinte, când mock-ul nostru face parte dintr-un cod mai complex care (poate) îi apelează cele două metode, la sfârșitul testului vrem să vedem că s-a apelat cel puțin o dată metoda execute
cu parametrul 2.
Vrem să implementăm următoarele strategii de verificare:
atLeastOnce()
- cel puțin o datăexactlyOnce()
- exact o datătimes(n)
- exact de n oriatLeastOnce
Exemplu de cod mai jos. Observați cum apelul complex primește un mock-object și nu un obiect “real”.
mock.execute(); callGoogleCrawlers(mock); // alt cod care în spate cheamă mock.execute() Mockito.verify(mock, Mockito.times(2)).execute();
Dacă verificarea eșuează, se va arunca un MockitoException (îl veți scrie voi). Mesajele pentru verificări eșuate vor fi:
“expected at least one call”
“expected ” + nr_apeluri_așteptate + “ calls, got ” + nr_apeluri_observate
Vrem să implementăm un “Mockito lite”, având comportamentul descris mai sus.
Ce avem:
Mockable<A, B>
- descrisă la începutVrem să permitem construcțiile:
Mockito.watch().when(…).thenReturn(…).thenThrow(…).thenReturn(…)….andBeDoneWithIt()
Mockito.verify(mock, Mockito.atLeastOnce()).execute(/*param sau nu*/)
Mockito.verify(mock, Mockito.exactlyOnce()).execute(/*param sau nu*/)
Mockito.verify(mock, Mockito.times(n)).execute(/*param sau nu*/)
Mockito.verify(mock).execute(/*param sau nu*/)
Mai vrem:
MockitoException
cu mesajele explicate mai sus dacă verificările de instrumentare eșueazăUnit
Sursele Java + Javadoc.
Orice din biblioteca standard Java. Chiar orice.
Observați că nu am menționat nicăieri de alte structuri, tipuri de date sau design patterns. Tot ce este obligatoriu este ca “sintaxa” construcțiilor pe care vi le cerem să compileze și să funcționeze corect. Aveți libertate deplină la structurarea codului, clase, interfețe, generics, ce și cum vreți.
Există și un fel de Java black magic Marauder's Map - Java Reflection. Nu vă încurajăm să îl folosiți - tema a fost gândită să o puteți rezolva cu ce ați învățat până acum. Dacă totuși sunteți curioși, există un laborator de reflection cu care vă puteți înarma. But beware - great power, great responsibility.
Alți colegi - mai mici, egali, sau mai mari.
Mockito per se. Mockito, de altfel, este un framework open source foarte bine proiectat, și vă puteți uita pe sursele sale dacă sunteți interesați de acest subiect.
Carrier has arrived.
Testele trebuie să compileze și să ruleze corect (metodele execute
trebuie să întoarcă true
).
Pe VMChecker.
Nu uitați de paginile wiki: indicații pentru teme şi coding style.
Aveți grijă la datele pe care le lăsați (din greșeală sau nu) pe calculatoarele de la laborator, pe stick-uri USB, pe Dropbox/Drive/etc.