Testam pentru a arata ca programul functioneaza. Acest aspect este logic, dar are cateva probleme:
Testarea este, in esenta, un proces de esantionare.
Ganditi-va la un sac plin cu pietre, pietre albe indicand o intrare pentru care programul reuseste, pietre negre indicand o intrare pentru care programul esueaza.
Acum sa presupunem ca ati scos pietre din geanta si ca ati vazut doar pietre albe.
Cate trebuie sa extragi inainte de a putea concluziona cu siguranta ca nu exista pietre negre in geanta?
De fapt, singura modalitate de a fi sigur de asta este sa scoti fiecare piatra din sac. Dar daca sacul are atat de multe pietre incat nu avem timp si rabdare sa scoatem mai mult decat un mic procent… Ei bine, pur si simplu nu putem fi siguri ca nu sunt mai multe pietre negre aflate in sac.
Deci „De ce testam?”. Testam pentru a castiga incredere in codul nostru. Daca alegem o multime de teste si le alegem intr-un mod inteligent, putem spune in mod legitim ca am invatat ceva. Poate ca nu putem spune ca nu au mai ramas bug-uri, dar putem spune cel putin ca programul functioneaza de cele mai multe ori.
Testele unitare sunt teste automatizate care asigura ca o aplicatie indeplinește cerintele de design si aplicatia ca se comporta conform specificatiilor.
In ceea ce priveste OOP, “unitatea” care urmeaza sa fie supusa testarii este adesea:
Crearea unui test se realizeaza prin adnotarea unei metode cu @Test.
@Test public void theySeeMeRollinTheyHatin() { /* code to test */ }
Sa presupunem ca avem o clasa care contine informatii despre un student:
public class Student { private String name; private Double gradeIC; private boolean lovesIC; private LocalDate birthday; public Student(String name, Double gradeIC, boolean lovesIC, LocalDate birthday) { this.name = name; this.gradeIC = gradeIC; this.lovesIC = lovesIC; this.birthday = birthday; } public void sprinkleSomeMagicNerdDust() { this.lovesIC = true; this.gradeIC = 10.0; } ... /* boilerplate code aka getters and setters */ ... }
Si dorim sa cream un test pentru metoda, constructor, getters si setters.
Pentru a realiza acest lucru, avem nevoie sa folosim cateva metode din biblioteca de JUnit. Cele mai relevante metode pe care le vom folosi pentru a testa, sunt:
assertNotEquals(Object unexpected, Object actual);
assertNotNull(Object actual);
Astfel, testul nostru o sa arate in modul urmator:
@Test public void noLongerATestNoobie() { Student student = new Student("Chris Rock", 7.0, false, LocalDate.of(1965, 2, 7)); assertNotNull(student); // True assertEquals(student.getGradeIC(), 7.0); // True assertEquals(student.getLovesIC(), false); // True student.sprinkleSomeMagicNerdDust(); assertNotEquals(student.getGradeIC(), 7.0); // True => no longer 7. the previous call changed the grade. assertEquals(student.getGradeIC(), 10.0); // True assertEquals(student.getLovesIC(), true); // True }
In cazul in care toate assert-urile o sa fie True, testul a trecut. In cazul in care oricare dintre assert-uri intoarce False, atunci testul o sa pice.
/* e.g. */ assertThat(student, instanceOf(Student.class));
/* e.g. */ assertAll("last name", () -> assertTrue(student.getName().startsWith("C")), () -> assertTrue(student.getName().endsWith("k")), () -> assertEquals(7.0, student.getGradeIC()));
/* e.g. */ NumberFormatException thrown = assertThrows(NumberFormatException.class, () -> { Integer.parseInt("One"); } , "NumberFormatException was expected"); Assertions.assertEquals("For input string: \"One\"", thrown.getMessage());
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*;
Sa spunem ca ne dorim sa existe un anumit set up pentru testele noastre, sau ca vrem avem un anumit comportament dupa finalizarea testului.
Am putea adauga aceste functionalitati in corpului metodei de test. Dar daca avem mai multe teste pentru care am vrea sa se intample acelasi lucru inainte sau dupa test?
Am ajunge in situatia in care am avea mult cod duplicat, iar asta ar incalca un pricipiu important de dezvoltare - DRY (Don't repeat yourself).
@BeforeEach void setUp() { System.out.println("Test started"); student = new Student("BoJack", 4.0, false, LocalDate.of(1964, 1, 2)); } @Test void isPassingIC_GradeLessThan5_False() { assertNotNull(student); asssertFalse("Bro, that Gandalf meme is getting old...", student.isPassing()); // grade is 4 < 5 => student not passing } @Test void isPassingIC_GradeGraterThan5_True() { assertNotNull(student); student.study(); // method body: student.grade = 10; asssertTrue("Ops, i did it again!", student.isPassing()); // grade is now 10 } @AfterEach void tearDown() { student.throwAParty(); System.out.println("So cool, this is!"); }
Code coverage este o metrica care arata cat de mult din cod este testat. Este o metrica foarte utila care ajuta in evaluarea calitatii testelor.
Valorile comune care apar in rapoartele de acoperire includ: