ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 150
Numărul 149 Numărul 148 Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 55
Abonament PDF

Comoara din suita JVM - JUnit 5

Mihai Anghel
Senior Java Developer @ Kindred Group



PROGRAMARE

Zilele sunt numărate pentru versiunea a 4-a a librăriei JUnit. Cel mai cuprinzător framework de testare în Java va fi îmbunătăţit în curând și ceea ce urmează îl face cu adevărat performant. JUnit 5 este mai mult decât o librărie, este o platformă pentru testare. Nu doar că rezolvă problemele versiunilor anterioare oferind o interfaţă îmbunătăţită, dar introduce un mecanism nou pentru executarea și descoperirea testelor. Arhitectura stratificată asigură o separare a componentelor și permite conectarea a diferite motoare de executare pentru a menţine compatibilitatea cu versiunile anterioare. Lansarea oficială este planificată pentru primul trimestru al anului 2017, dar până atunci să aruncăm o privire și să identificam motivele pentru care poate fi un candidat bun pentru unit-testing în proiectul următor.

În prima parte, vom urmări cum se scriu testele și vom identifica îmbunătăţirile pe care le aduce versiunea a 5-a și vom încheia prin a înţelege motivele pentru care noua arhitectură este inovatoare.

Migrarea de la JUnit 4

1. Adnotări

Majoritatea adnotărilor sunt redenumite, iar clasele se pot găsi în pachetul org.junit.juniper.api.

JUnit 4 JUnit 5
@Before @BeforeEach
@After @AfterEach
@BeforeClass @BeforeAll
@AfterClass @AfterAll
@Ignored @Disabled("reason")
@RunWith @ExtendWith

2. Aserțiuni

Clasele de aserţiuni au fost mutate din pachetul org.junit.Assert în org.junit.jupiter.api. Assertions și au fost aduse trei îmbunătăţiri.

a) Mesajul în cazul unei erori a fost mutat ultimul în lista de parametri.

 assertEquals(expected, actual, "error message");

b) Expresii lambda

 assertTrue(false, () -> "Error. Expected true");

Exemplul precedent este trivial, dar expresiile lambda fiind evaluate cu întârziere economisesc resurse și timp în cazul în care logica de construcție a mesajului este costisitoare.

c) Asețiuni multiple

 assertAll("Person",
  () -> assertEquals("John", person.getFirstName()),
  () -> assertEquals("Doe", person.getSurname()),
  () -> assertEquals("SW19 5HP", person.getPostCode()));

Această îmbunătăţire este foarte utilă deoarece în precedentele versiuni ale librăriei, execuţia se opreşte după prima evaluare eșuată și nu oferă un rezultat final al aserțiunilor din test. În cazul unei erori, utilizatorului îi va fi afișat următorul mesaj:

AssertionsTest.multipleAssertionsTest:24 Person (2 failures)
  expected: <Doe> but was: <Toe>
  expected: <SW19 5HP> but was: <NW19 3RU>

3. Etichetarea și dezactivarea testelor

Testele pot fi etichetate cu adnotarea @Tag și dezactivate cu @Disabled adăugate la nivelul clasei sau metodei.

4. Supoziții (Assumptions)

Acestea sunt folosite în situaţiile în care aserţiunile trebuie evaluate doar dacă anumite criterii sunt îndeplinite:

 @Test
 void shouldEvaluateSeniors() {
  assumeTrue(person.getAge > 65);
  assertAll("Person",
   () -> assertEquals("John", person.getFirstName()),
   () -> assertEquals("Doe", person.getSurname()));
 }

5. Excepții

Testarea excepțiilor a fost ignorată de cele mai multe ori sau a fost realizată superficial. Vestea bună este aceea că JUnit 5 permite o testare facilă a tipului și a mesajului excepției.

 @Test
 void testException() {
  Throwable e = assertThrows(RuntimeException.class, () -> target.throwEx());
  assertEquals("message", e.getMessage());
 }

6. Teste imbricate (nested)

Librăria JUnit 5 introduce testele imbricate pentru a exprima relațiile complexe între diferite grupuri de teste. De asemenea, permite dezvoltarea într-un stil BDD prin adnotarea testelor cu @DisplayName și atribuirea unui nume lizibil. Clasele imbricate sunt marcate cu adnotarea @Nested pentru a permite integrarea corectă a acestora. Documentaţia oficială prezintă un exemplu foarte bun de astfel de teste.

7. Testarea dinamică

Testele dezvoltate în JUnit 4 sunt statice, în sensul în care acestea sunt definite complet în momentul compilării. Ideea nouă care a fost adusă în ultima versiune introduce un nou tip de test, cel dinamic, care este generat la runtime de către o metodă de tip factory adnotată cu @TestFactory. Această metodă nu reprezintă un test în adevăratul sens al cuvântului, dar este folosită pentru a genera în mod dinamic testele și poate returna Stream, Collection sau Iterable de instanțe ale clasei DynamicTest. Această clasă este @FunctionalInterface ceea ce înseamnă că implementarea testului poate fi creată printr-o expresie lambda.

 @TestFactory
 Collection<DynamicTest> generateDynamicTests() {
  return Arrays.asList(
   DynamicTest.dynamicTest("Test status", () -> assertTrue(p.isMarried())),
   DynamicTest.dynamicTest("Test age", () -> assertEquals(30, p.getAge()))
  );
 }

Avantajul testării dinamice apare când se adoptă testarea data driven.

8. Testarea data driven

Acest tip de testare permite excutarea de multiple iterații ale aceluiași test cu date de intrare diferite pentru a evita duplicarea codului. Această procedură se numește table driven testing în alte librării de testare sau limbaje de programare precum Go, Spock sau Cucumber. În JUnit 5 putem implementa astfel de teste cu ajutorul metodelor de tip factory prin parcurgerea unei colecții de date de test și crearea dinamică a testelor pentru fiecare set de date.

 @TestFactory
 Collection<DynamicTest> generateDynamicTests() {
  return testData()
   .stream()
   .map(d -> dynamicTest("Data:" + d, () -> {
     /** implementation */
   }))
  .collect(Collectors.toList());
 }

9. Extensii

După cum ne spune și numele, această funcționalitate permite programatorilor să administreze ciclul de viaţă al testelor și să extindă logica acestora fără a modifica funcționalitatea existentă. Acest lucru poate fi implementat cu așa numitele puncte de extensie (extension points) care sunt oferite în mod implicit. Din punct de vedere tehnic, acestea sunt interfețe în a căror implementare se definește logica de extensie. Implementările se numesc extensii și acestea pot fi înregistrate folosind adnotarea @ExtendWith la nivelul clasei sau metodei extinse.

Pentru a folosi extensiile trebuie să importăm pachetul org.junit.jupiter.api.extension.

Punct de extensie Rol
TestInstancePostProcessor Definește logica de postprocesare a
testului.
ContainerExecutionCondition Definește interfața pentru execuția
condiționată a containerului.
ParameterResolver Definește interfața pentru rezolvarea
dinamică a parametrilor la runtime.
TestExecutionExceptionHandler Definește interfața pentru a manipula
excepțiile din timpul execuției
testelor.
BeforeAllCallback, BeforeEachCallback, Definesc interfețe pentru administrarea
AfterAllCallback, AfterEachCallback ciclului de viață al testelor.

Arhitectura

JUnit 4 a fost lansat în urmă cu aproximativ 12 ani. De atunci, numeroase lucruri au evoluat în domeniul programării de la Java și JVM pana la IDE-uri și instrumentele de CI. Testele au devenit din ce în ce mai importante în munca de zi cu zi a programatorilor, rezultatele rapide și navigarea facilă fiind elemente cheie ale unei librării performante. Mai mult decât atât, IDE-urile și instrumentele de CI au devenit dependente de detaliile de implementare ale librăriei JUnit pentru a se integra cu aceasta. JUnit 4.12 (ultima versiune) este un artefact monolitic care conține doar interfața pentru testare și motorul de executare al testelor. Mecanismul de descoperire al testelor trebuie implementat separat de către fiecare instrument. Johannes Link, unul dintre iniţiatorii noului proiect, menționa într-un interviu "succesul JUnit ca o platforma previne dezvoltarea JUnit ca un instrument de testare" ("the success of JUnit as a platform prevents the development of JUnit as a test tool"). Ceea ce noul proiect (denumit iniţial JUnit Lambda) a încercat sa rezolve a fost separarea interfeței publice de motorul de executare.

Noua platformă de testare are două părți importante:

  1. interfața (API) pentru dezvoltarea testelor

  2. mecanismul pentru descoperirea și executarea testelor

Despre prima parte am discutat în introducerea articolului, iar în continuare vom detalia modul în care arhitectura a fost realizată. Aceasta este compusă din trei module, determinând astfel o arhitectură decuplată a noii platforme:

  1. un motor pentru descoperirea și executare al testelor;

  2. o interfaţă comună pe care toate motoarele trebuie să o implementeze pentru uniformitate;

  3. un mecanism de orchestrare al motoarelor.

Această abordare de decuplare conduce la arhitectura prezentată în Figura 1.

Figura 1 - Junit 5 Architecture

junit-juniper-api - interfața folosită de programatori pentru dezvoltarea testelor în JUnit 5;

junit-juniper-engine - implementare a junit-platform-engine care execută testele JUnit 5;

junit-vintage-engine - implementare a junit-platform-engine care execută testele JUnit 4;

junit-platform-engine - interfața comună pe care toate motoarele trebuie să o implementeze și să o utilizeze pentru a se înregistra la platforma de lansare (platform-launcher);

junit-platform-launcher - orchestrează motoarele, folosește ServiceLoader pentru descoperirea diferitelor implementări ale motorului (platform-engine) și oferă o interfață instrumentelor pentru a interacționa cu executarea testelor.

Diagrama precedentă poate fi sumarizată ca JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage. Trebuie menționat faptul că platforma oferă utilizatorului flexibilitatea de a folosi propriul motor de testare prin simpla implementare a interfețelor din artefactul junit-platform-engine și înregistrarea acestuia. Compatibilitatea cu versiunile anterioare (a 3-a și a 4-a) este realizată prin motorul JUnit Vintage, iar avantajul este că acesta poate exista în proiect simultan cu JUnit Juniper.

Ca o notă de final, pentru o inspectare a acestei platforme trebuie incluse în fișierul de configurare artefactele junit-juniper-api în secțiunea de dependențe și junit-juniper-engine în secțiunea de plugin.

Concluzii

După cum am observat, s-a realizat un progres major în ultima versiune de JUnit. Cea mai folosită librărie pentru testare în ecosistemul JVM a fost suferit un upgrade serios și a făcut un pas important spre a deveni o platformă de testare. Interfața îmbunătăţită a rezolvat principalele probleme din versiunile anterioare, iar arhitectura stratificată permite o separare clară a componentelor și chiar oferă fundamentele pentru folosirea unui motor de testare propriu.

NUMĂRUL 149 - Development with AI

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects