Lab 1

JUnit

JUnit este un framework simplu care permite scrierea de teste unitare repetitive. O suita de teste unitare de calitate, usor de inteles si care ofera o acoperire cat mai completa a codului, din perspectiva a mai multe si diverse metrici de acoperire, ramane una dintre cele mai bune practice ale oricarui proces de dezvoltare software, asigurand stabilitatea si calitatea produsului software rezultat.

Pentru a putea folosi JUnit 5, trebuie importate

   1 import org.junit.jupiter.api.*;

Adnotarile in JUnit 5

In cele ce urmeaza, vor fi prezentate cele mai frecvent utilizate adnotari suportate de JUnit 5, urmand ca fiecare in parte sa fie descrisa mai in detaliu.

   1 @BeforeEach
   2 @BeforeAll
   3 @AfterEach
   4 @AfterAll
   5 @Test
   6 @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
   7 @Disabled

Adnotarile @BeforeEach and @AfterEach

In JUnit 5 nu mai avem metodele conventionale setUp() and tearDown() din JUnit 3. In schimb, au fost introduse adnotarile @BeforeEach si @AfterEach, care indeplinesc acelasi scop. Practic, utilizand adnotarea @BeforeEach pe o metoda, acea metoda se va comporta asemenea metodei setUp() din JUnit 3, executandu-se intotdeauna inainte de fiecare test unitar, cu scopul initializarii mediului de testare. Analog, o metoda adnotata cu @AfterEach se va comporta la fel ca metoda tearDown() din JUnit 3 si va rula mereu dupa executia fiecarui test unitar, cu scopul de a restabili starea anterioara a mediului de testare.

   1     @BeforeEach
   2     public void setUp() {
   3         System.out.println("@BeforeEach method will execute before every JUnit 5 test!");
   4     }
   5  
   6     @AfterEach
   7     public void tearDown() {
   8         System.out.println("@AfterEach method will execute after every JUnit 5 test!");
   9     }

Adnotarile @BeforeAll and @AfterAll

Adnotarile @BeforeAll si @AfterAll functioneaza in mod similar cu adnotarile @BeforeEach si @AfterEach, dar cu o diferenta foarte importanta: metodele adnotate cu @BeforeAll sau @AfterAll se vor executa o singura data per suita de teste (clasa de teste unitare). Aceste adnotari se utilizeaza pentru a realiza initializarile necesare la nivel de suita de teste (in loc de la nivel de test individual, cum fac @BeforeEach si @AfterEach).

   1     @BeforeAll
   2     public static void setUpClass() throws Exception {
   3         System.out.println("@BeforeAll method will be executed before JUnit test for a Class starts");
   4     }
   5 
   6     @AfterAll
   7     public static void tearDownClass() throws Exception {
   8          System.out.println("@AfterAll method will be executed after JUnit test for a Class Completed");
   9     }

Adnotarea @Test

Adnotarea @Test inlocuieste conventia anterioara din JUnit 3 de a adauga prefixul test la numele fiecarei metode de test. In versiunile timpurii de JUnit era necesar ca o clasa de teste JUnit sa extinda org.junit.TestCase, si, in plus, ca fiecare metoda ce descrie un test unitar sa aiba un nume care sa inceapa cu prefixul test. In schimb, in JUnit 5, este nevoie doar sa adaugam adnotarea @Test pe fiecare metoda de test, ca in exemplul de mai jos.

   1   @Test
   2   public void testMethod() {
   3       System.out.println("");
   4   }

@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)

Uneori se poate intampla sa avem teste care ruleaza o perioada indelungata de timp. Pentru a putea limita durata de executie a unui test in timp, JUnit 5 ne pune la dispozitie o alta adnotarea, si anume @Timeout, care primeste o valoare intreaga, exprimata in milisecunde (unitatea de masura se poate schimba). Aceasta adnotare este foarte utila, mai ales atunci cand se doreste testarea unor metode implicand servicii al caror raspuns trebuie sa se incadreze intr-o perioada predefinita de timp.

   1   @Test
   2   @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
   3   public void failsIfExecutionTimeExceeds500Milliseconds() {
   4       // fails if execution time exceeds 500 milliseconds.
   5   }

Testare Exceptii

O alta constructie extrem de folositoare este accea de testare/tratare a exceptiilor. Pentru a testa ca o anumita secventa de cod arunca intradevar exceptia la care ne asteptam / care e conform specificatiilor, se foloseste assertThrows, indicand clasa exceptiei (a se observa sintaxa folosita: NumeClasaExceptie.class).

   1   @Test
   2   public void exceptionTesting() {
   3       Exception exception = assertThrows(ArithmeticException.class, () -> calculator.divide(1, 0));
   4       assertEquals("/ by zero", exception.getMessage());
   5   }

@Disabled

Cateodata scriem teste unitare fara a avea implementarea clasei testate in totalitate (de exemplu daca facem Test Driven Development). Se poate intampla ca la un moment dat sa vrem sa pastram testul unitar pentru care nu avem inca implementarea metodei, dar totodata vrem ca rezultatul tuturor testelor (suitei de teste) sa fie fara erori. In acest caz putem adnota metoda respectiva cu @Disabled("Not yet implemented"), caz in care ele nu vor fi rulate impreuna cu restul testelor. JUnit 5 ignora toate metodele de testare adnotate cu @Disabled.

   1   @Test
   2   @Disabled("for demonstration purposes")
   3   public void skippedTest() {
   4      // not executed
   5   }

Assert-urile in JUnit 5

Framework-ul JUnit 5 dispune de numeroase variante de assert-uri. Le puteti regasi mai jos pe cele mai des folosite.

   1 assertEquals(expected, actual, "failure - objects not same (mesaj optional)");
   2 assertArrayEquals(expected, actual, "failure - byte arrays not same (mesaj optional)");
   3 assertTrue(true, "success - this is true");
   4 assertFalse(false, "failure - should be false");
   5 assertNull(null, "should be null");
   6 assertNotNull(new Object(), "should not be null");
   7 assertSame(aNumber, aNumber, "should be same");
   8 assertNotSame(new Object(), new Object(), "should not be same Object");
   9 assertThrows(NumeClasaExceptie.class, () -> {});

E important de observat ca aproape toate assert-urile JUnit 5 au aceeasi parametri, in aceeasi ordine standard: valoarea asteptata / dorita, valoarea actuala obtinuta la executie si mesaj optional de eroare in cazul in care testul pica.

Pentru mai multe detalii despre testarea unitara cu JUnit va rugam sa accesati link-urile de mai jos.

Urmați principiile de mai jos

  • implementați īn paralel codul și testele
  • dați testelor nume semnificative - care sa reflecte/descrie ce intenționați să testați
  • scrieți cate UN test pentru fiecare lucru pe care doriți să īl testați

Exemplu

   1   public class Function {
   2      public int calculate(int a, int b) {
   3          if (b == 0) {
   4              return -1;
   5          }
   6
   7          if (a > b) {
   8              return a + b;
   9          }
  10          
  11          return a / b;
  12      }
  13   }
   1   import org.junit.jupiter.api.BeforeEach;
   2   import org.junit.jupiter.api.Test;
   3   import static org.junit.jupiter.api.Assertions.assertEquals;
   4   class FunctionTest {
   5
   6       private Function function;
   7       @BeforeEach
   8       void setup() {
   9           function = new Function();
  10       }
  11
  12       @Test
  13       void givenBEqualTo0_whenCalculate_thenResultIsMinusOne() {
  14           int calculate = function.calculate(2, 0);
  15           assertEquals(-1, calculate);
  16       }
  17
  18       @Test
  19       void givenAGreaterThanB_whenCalculate_thenResultIsSumOfAAndB() {
  20           int calculate = function.calculate(2, 1);
  21           assertEquals(3, calculate);
  22       }
  23
  24       @Test
  25       void givenALessThanB_whenCalculate_thenResultIsDivisionOfAAndB() {
  26           int calculate = function.calculate(1, 2);
  27           assertEquals(0, calculate);
  28       }
  29   }

Exercitiul 1

Implementati si testati clasa al carei schelet il aveti mai jos la dispozitie. Metoda care va fi testata este, desigur, strToInt.

   1 public class StringConv {
   2  public int strToInt(String str) throws NumberFormatException {
   3    return 0;
   4  }
   5 }

Date de test obligatorii: "0", "1", "", "8". Sunteti, desigur, liberi sa scrieti orice alte teste suplimentare considerati potrivite.

Mai jos puteti gasi cateva metode utile (cu exemple de utilizare) pentru implementarea metodei strToInt,

   1  String str = "unString";
   2  str.charAt(x); // returneaza char-ul de la pozitia x din str
   3  str.length(); // returneaza lungimea sirului str
   4  Character.isDigit(char c); 
   5  Math.pow(baza,exponentul);

Implementarea de baza a metodei testate

   1 public class StringConv {
   2  public int strToInt(String str) throws NumberFormatException {
   3   int sum=0;
   4    for (int i = 0; i < str.length(); i++) {
   5      int digit = (int)str.charAt(str.length()-i-1) - (int)'0';
   6      sum = sum + digit * (int)Math.pow(10, i);
   7    }
   8    return sum;
   9  }
  10 }

Exercitiul 2

Consideram clasa de mai jos

   1 class VVSDate {
   2     public VVSDate(int day, int month, int year);
   3     public int getDays(VVSDate other);
   4 }

Construiți o clasă VVSDate cu o metodă care returnează distanța īn zile (pozitivă sau negativă) pānă la o alta dată.

Completaţi pānă la o suită de teste pe care o consideraţi suficientă pentru toate cazurile reprezentative.

Dacă īn cursul programării se definesc alte metode ajutătoare se vor scrie teste unitare īnaintea implementării metodelor (de ex. metoda care să spună dacă un an e bisect sau nu).

   1 public class VVSDate {
   2         private int day,month,year;
   3 
   4         public VVSDate(int day, int month, int year) {
   5                 this.day = day;
   6                 this.month = month;
   7                 this.year = year;
   8         }
   9 
  10         public int getDays(VVSDate other) {
  11                 return daysSinceZero() - other.daysSinceZero();
  12         }
  13 
  14         private int daysSinceZero() {
  15                 int[] daySums = new int[]{0,31,59,90,120,151,181,212,243,273,304,334,365};
  16                 int d = day + daySums[month-1] + (year*365) + year/4 - year/100 + year/400;
  17                 if ((year%400==0 || (year%4==0 && year%100!=0)) && month<=2)
  18                         d--;
  19                 return d;
  20         }
  21 
  22 }