Administrativ
Laboratoare
Tema
Teste
Resurse utile
Alte resurse
Arhiva Teme
Administrativ
Laboratoare
Tema
Teste
Resurse utile
Alte resurse
Arhiva Teme
This is an old revision of the document!
Unit testing-ul s-a impus în ultima perioadă în dezvoltarea proiectelor scrise în limbajul Java şi nu numai, pe măsura apariţiei unor utilitare gratuite de testare a claselor, care au contribuit la creşterea vitezei de programare şi la micşorarea semnificativă a numărului de bug-uri.
Printre avantajele folosirii framework-ului JUnit se numără:
Reprezintă un framework de Unit Testing pentru Java.
Exemplu:
public class Student { private String name; private String age; public Student(String name, String age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } }
import java.util.ArrayList; import java.util.List; public class Grupa { List<Student> students; Grupa () { students = new ArrayList<Student>(); } public List<Student> getStudents() { return students; } public void setStudents(List<Student> students) { this.students = students; } public void addStudent(Student student) { students.add(student); } public Student getStudent(String name) { for (Student st : students) { if (null != st.getName() && st.getName().equals(name)) { return st; } } return null; } public boolean areStudentsInGroup() { if (0 == students.size()) { return false; } return true; } }
import org.junit.Assert; import org.junit.Before; public class Test { private Grupa grupa; @Before public void setup() { grupa = new Grupa(); } @org.junit.Test public void testNoStudentInGroup() { Assert.assertEquals(false, grupa.areStudentsInGroup()); } @org.junit.Test public void testAddStudent() { Student st = new Student("Elena", "11"); grupa.addStudent(st); Assert.assertTrue(grupa.getStudent("Elena").equals(st)); } @After void tearDown() { grupa = null; } }
Observaţii:
@Test
@Before
) şi tearDown (având adnotarea: @After
) se apelează înainte, respectiv după fiecare test. Se întrebuinţează pentru iniţializarea/eliberarea resurselor ce constituie mediul de testare, evitându-se totodată duplicarea codului şi respectându-se principiul de independenţă a testelor. In exemplul dat ordinea este:@Before setUp
@Test testNoStudentInGroup
@After tearDown
@Before setUp
@Test testAddStudent
@After tearDown
assert
:assertTrue
assertFalse
assertEquals
assertNull
/ assertNotNull
Pentru a importa un jar într-un proiect din Eclipse
parcurgeţi următorii paşi: click dreapta proiect → Build path → Configure build path → Libraries → Add jars (Add external jars).
Câteodată avem nevoie să testăm funcționalitatea unor clase ce folosesc metode din alte clase netestate sau care nu pot fi testate. De asemenea, există cazuri în care vrem sa testăm comportamentul clasei în situații extreme sau foarte greu de replicat (erori de disc, epuizarea spațiului pe disc, obținerea unei anumite valorii în cazul in care folosim generatoare random).
Pentru a rezolva ușor aceste necesități putem folosi obiecte de tip mock. Aceste obiecte simulează comporatamentul clasei mock-uite și sunt controlate din interiorul unit testelor. Pentru mai multe detalii puteți consulta pagina Mock Object.
În esenţă, o excepţie este un eveniment care se produce în timpul execuţiei unui program şi care perturbă fluxul normal al instrucţiunilor acestuia.
De exemplu, în cadrul unui program care copiază un fişier, astfel de evenimente excepţionale pot fi:
O abordare foarte des intâlnită, ce precedă apariţia conceptului de excepţie, este întoarcerea unor valori speciale din funcţii care să desemneze situaţia apărută. De exemplu, în C, funcţia fopen întoarce NULL dacă deschiderea fişierului a eşuat. Această abordare are două dezavantaje principale:
int openResult = open(); if (openResult == FILE_NOT_FOUND) { // handle error } else if (openResult == INUFFICIENT_PERMISSIONS) { // handle error } else {// SUCCESS int readResult = read(); if (readResult == DISK_ERROR) { // handle error } else { // SUCCESS ... } }
Mecanismul bazat pe excepţii înlătură ambele neajunsuri menţionate mai sus. Codul ar arăta aşa:
try { open(); read(); ... } catch (FILE_NOT_FOUND) { // handle error } catch (INUFFICIENT_PERMISSIONS) { // handle error } catch (DISK_ERROR) { // handle error }
Se observă includerea instrucţiunilor ce aparţin fluxului normal de execuţie într-un bloc try şi precizarea condiţiilor excepţionale posibile la sfârşit, în câte un bloc catch. Logica este următoarea: se execută instrucţiune cu instrucţiune secvenţa din blocul try şi, la apariţia unei situaţii excepţionale semnalate de o instrucţiune, se abandonează restul instrucţiunilor rămase neexecutate şi se sare direct la blocul catch corespunzător.
Când o eroare se produce într-o funcţie, aceasta creează un obiect excepţie şi îl pasează către runtime system
. Un astfel de obiect conţine informaţii despre situaţia apărută:
Pasarea menţionată mai sus poartă numele de aruncarea (throwing) unei excepţii.
Exemplu de aruncare a unei excepţii:
List<String> l = getArrayListObject(); if (null == l) throw new Exception("The list is empty");
În acest exemplu, încercăm să obţinem un obiect de tip ArrayList
; dacă funcţia getArrayListObject
întoarce null, aruncăm o excepţie.
Pe exemplul de mai sus putem face următoarele observaţii:
new
)
În realitate, clasa Exception
este părintele majorităţii claselor excepţie din Java. Enumerăm câteva excepţii standard:
null
).Iterator
care nu mai conţine un element următor.
În momentul în care se instanţiază un obiect-excepţie, în acesta se reţine întregul lanţ de apeluri de funcţii prin care s-a ajuns la instrucţiunea curentă. Această succesiune se numeşte stack trace şi se poate afişa prin apelul e.printStackTrace(), unde e
este obiectul excepţie.
Când o excepţie a fost aruncată, runtime system
încearcă să o trateze (prindă). Tratarea unei excepţii este făcută de o porţiune de cod specială.
Să observăm următorul exemplu:
public void f() throws Exception { List<String> l = null; if (null == l) throw new Exception(); } public void catchFunction() { try { f(); } catch (Exception e) { System.out.println("Exception found!"); } }
Se observă că dacă o funcţie aruncă o excepţie şi nu o prinde trebuie, în general, să adauge clauza throws în antet.
Funcţia f
va arunca întotdeauna o excepţie (din cauza că l
este mereu null
). Observaţi cu atenţie funcţia catchFunction
:
try
, în interiorul căruia se apelează f
. De obicei, pentru a prinde o excepţie, trebuie să specificăm o zonă în care aşteptăm ca excepţia să se producă (guarded region). Această zonă este introdusă prin try
.catch
(Exception e
). La producerea excepţiei, blocul catch
corespunzător va fi executat. În cazul nostru se va afişa mesajul “S-a generat o excepţie”
.Observaţi un alt exemplu:
public void f() throws MyException, WeirdException { List<String> l = null; if (null == l) throw new MyException(); throw new WeirdException("This exception never gets thrown"); } public void catchFunction() { try { f(); } catch (MyException e) { System.out.println("Null Pointer Exception found!"); } catch (WeirdException e) { System.out.println("WeirdException found!"); } }
În acest exemplu funcţia f
a fost modificată astfel încât să arunce MyException
. Observaţi faptul că în catchFunction
avem două blocuri catch
. Cum excepţia aruncată de f
este de tip MyException
, numai primul bloc catch
se va executa.
Prin urmare:
try
şi catch
throws
.
Dacă o excepţie nu este tratată nici în main
, aceasta va conduce la încheierea execuţiei programului!
În general, vom dispune în acelaşi bloc try-catch
instrucţiunile care pot fi privite ca înfăptuind un acelaşi scop. Astfel, dacă o operaţie din secvenţa esuează, se renunţă la instrucţiunile rămase şi se sare la un bloc catch
.
Putem specifica operaţii opţionale, al căror eşec să nu influenţeze întreaga secvenţă. Pentru aceasta folosim blocuri try-catch
imbricate:
try { op1(); try { op2(); op3(); } catch (Exception e) { ... } op4(); op5(); } catch (Exception e) { ... }
Dacă apelul op2
eşuează, se renunţă la apelul op3
, se execută blocul catch
interior, după care se continuă cu apelul op4
.
Presupunem că în secvenţa de mai sus, care deschide şi citeşte un fişier, avem nevoie să închidem fişierul deschis, atât în cazul normal, cât şi în eventualitatea apariţiei unei erori. În aceste condiţii se poate ataşa un bloc finally
după ultimul bloc catch
, care se va executa în ambele cazuri menţionate.
Secvenţa de cod următoare conţine o structură try-catch-finally
:
try { open(); read(); ... } catch (FILE_NOT_FOUND) { // handle error } catch (INUFFICIENT_PERMISSIONS) { // handle error } catch (DISK_ERROR) { // handle error } finally { // close file }
Blocul finally
se dovedeşte foarte util când în blocurile try-catch
se găsesc instrucţiuni return. El se va executa şi în acest caz, exact înainte de execuţia instrucţiunii return, aceasta fiind executată ulterior.
Nu toate excepţiile trebuie prinse cu try-catch
. Pentru a înțelege de ce, să analizăm clasificarea excepţiilor:
Checked exceptions, ce corespund clasei Exception:
FileNotFoundException
.Errors, ce corespund clasei Error:
IOError
.stack trace
).Runtime Exceptions, ce corespund clasei RuntimeException:
null
va produce NullPointerException
. Fireşte, putem prinde excepţia. Mai natural însă ar fi să eliminăm din program un astfel de bug care ar produce excepţia.try-catch
. Toate excepţiile sunt checked cu excepţia celor de tip Error, RuntimeException şi subclasele acestora, adica cele de tip unchecked.
Excepţiile error nu trebuie (în mod obligatoriu) prinse folosind try-catch
. Opţional, programatorul poate alege să le prindă.
Excepţiile runtime nu trebuie (în mod obligatoriu) prinse folosind try-catch
. Ele sunt de tip RuntimeException. Aţi întâlnit deja exemple de excepţii runtime, în urma diferitelor neatenţii de programare: NullPointerException
, ArrayIndexOutOfBoundsException
, etc.
Putem arunca RuntimeException
fără să o menţionăm în clauza throws din antet:
public void f(Object o) { if (o == null) throw new NullPointerException("o is null"); }
Când aveţi o situaţie în care alegerea unei excepţii (de aruncat) nu este evidentă, puteţi opta pentru a scrie propria voastră excepţie, care să extindă Exception, RuntimeException sau Error.
Exemplu:
class TemperatureException extends Exception {} class TooColdException extends TemperatureException {} class TooHotException extends TemperatureException {}
În aceste condiţii, trebuie acordată atenţie ordinii în care se vor defini blocurile catch. Acestea trebuie precizate de la clasa excepţie cea mai particulară, până la cea mai generală (în sensul moştenirii). De exemplu, pentru a întrebuinţa excepţiile de mai sus, blocul try-catch
ar trebui să arate ca mai jos:
try { ... } catch (TooColdException e) { ... } catch (TemperatureException e) { ... } catch (Exception e) { ... }
Afirmaţia de mai sus este motivată de faptul că întotdeauna se alege primul bloc catch care se potriveşte cu tipul excepţiei apărute. Un bloc catch referitor la o clasă excepţie părinte, ca TemperatureException
prinde şi excepţii de tipul claselor copil, ca TooColdException
. Poziţionarea unui bloc mai general înaintea unuia mai particular ar conduce la ignorarea blocului particular.
try { ... } catch(IOException | FileNotFoundException ex) { ... }
Metodele suprascrise (overriden) pot arunca numai excepţiile specificate de metoda din clasa de bază sau excepţii derivate din acestea.
Animal
şi clasa Zoo
. Clasa Zoo
conţine un vector de animale. Implementaţi metodele: addAnimal(Animal a)
, removeAnimal(Animal a)
, boolean areAnimals(), getAnimals()
, size()
. Creaţi o clasa Test
unde veţi verifica diverse scenarii:Zoo
.testAddAnimal
- adaugă un obiect Animal
şi verifică daca adăugarea a avut loc cu succes. Folosiţi: assertEquals
testRemoveAnimal
- folosiţi assertTrue
testAreAnimalsInZoo
- testul pică dacă metoda returnează false
. Hint: Assert.fail()
testGetAnimals
- adăugaţi două obiecte Animal
. Verificaţi ca adăugarea a avut loc cu succes. Folosiţi assertFalse.GeometricForms
avand un constructor ce primeste un String ce poate fi unul din valorile enum-ului Forms
.isTriangle
, isCircle
si isRectangle
au drept scop evaluarea stării obiectului GeometricForms
.GeometricFormsTest
.public enum Forms { TRIANGLE, CIRCLE, RECTANGLE }
Calculator
, ce conţine trei metode:add
: primeşte doi întregi şi întoarce un întregdivide
: primeşte doi întregi şi întoarce un întregaverage
: primeşte o colecţie ce conţine obiecte Integer
, şi întoarce media acestora ca un numar de tip întreg. Pentru calculul mediei, sunt folosite operaţiile add
şi divide
.add
:OverflowException
: este aruncată dacă suma celor doua numere depăşeşte Integer.MAX_VALUE
UnderflowException
: este aruncată dacă suma celor doua numere este mai mică decat Integer.MIN_VALUE
Calculator
.finally
chiar şi în cazul unui return
din metoda.