JUnit este un framework de testare pentru limbajul de programare Java. Versiunea JUnit 5 a fost reproiectată să rezolve anumite probleme particulare ale precedentelor versiuni. De asemenea, oferă o nouă arhitectură, cu posibilitatea de a crea a ierarhie de teste, cu assertions şi assumptions, cu teste dinamice şi parametrizate. Acest articol este o scurtă introducere în JUnit 5, pentru a pune la dispoziţie cititorului, posibilitatea de a înţelege noua arhitectură şi noile funcţionalităţi pe care să le exploreze mai departe.
JUnit, ca framework de testare unitară pentru limbajul de programare Java, este o unealtă foarte importantă pentru abordarea test-driven development (dezvoltare ghidată de teste). Este parte a unei familii de frameworkuri de testare, numite colectiv xUnit.
JUnit este legat la compilare ca și JAR, fiind cel mai frecvent inclusă bibliotecă externă în proiectele Java.
TDD (Test Driven Development) este un proces de dezvoltare software care se bazează pe repetarea unui scurt ciclu: mai întâi, cerinţele sunt transformate în test-case-uri specifice; apoi, software-ul este îmbunătăţit doar să treacă noile teste. Acest lucru este diferit de dezvoltarea care permite software-ului să fie creat fără a dovedi că îndeplineşte cerinţele.
Programatorul este ghidat de ţinte clare.
Codul este mai sigur.
Codul incorect poate fi izolat.
Noile funcţionalităţi pot fi introduse mai uşor.
JUnit 4 apărut în 2006, pune la dispoziţie o arhitectură simplă şi monolitică. Întreaga funcţionalitate este concentrată într-un singur fişier JAR. În ciuda aparentei sale simplităţi, au apărut o serie de probleme, care s-au agravat pe măsura trecerii timpului.
Faptul că API-ul existent nu este flexibil a forţat IDE-urile şi uneltele care utilizau JUnit să fie puternic cuplate cu acesta. Era nevoie să se acceseze implementarea internă a JUnit sau chiar să se utilizeze reflexia pentru a obţine informaţia dorită.
Aşadar, de vreme ce acelaşi fişier JAR era utilizat de toată lumea şi IDE-urile erau puternic cuplate cu acesta, posibilităţile de evoluţie ale JUnit erau serios reduse. Schimbarea unei variabile sau a unei metode private putea să îi afecteze pe cei care o utilizau din exterior. Un nou API proiectat pentru astfel de unelte, şi o nouă arhitectură erau necesare pentru a asigura evoluţia ulterioară.
O nouă abordare, una modulară, era necesară pentru a permite evoluţia JUnit. Separarea logică solicită:
Un API pentru scrierea testelor, dedicat în primul rând dezvoltatorilor;
Un mecanism pentru descoperirea şi rularea testelor;
În consecinţă, arhitectura JUnit 5 rezultată conţine trei module:
JUnit Platform, care serveşte pentru lansarea frameworkului de testare pe maşina virtuală Java. De asemenea, oferă un API pentru lansarea testelor de la consolă, din IDE-uri sau din alte unelte.
JUnit Jupiter este o combinaţie între noul model de programare şi modelul de extensie pentru scrierea testelor şi a extensiilor în JUnit 5. Numele a fost ales de la a cincea planetă a sistemului solar, care este şi cea mai mare.
JUnit Vintage oferă un motor de testare pentru a rula teste JUnit 3 şi JUnit 4 pe noua platformă, asigurând şi necesara compatibilitate înapoi.
Pasul spre JUnit 5
Pentru a putea utiliza JUnit 5 într-un proiect Java, următoarele dependenţe trebuie adăugate la configuraţia Maven:
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.0.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.0.1</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies>
Următoarele diferenţe importante există între JUnit 4 şi JUnit 5:
Spre deosebire de JUnit 4, clasa de test şi metodele de test pot fi package private. Un test simplu arată astfel:
class TestFixturesExample {
private static
HeavyResourceRequiredForAllTests heavyResource;
private TestSetup testSetup;
private SystemUnderTest systemUnderTest;
@BeforeAll
static void willBeDoneBeforeAllTests() {
System.out.println("@BeforeAll");
heavyResource =
new HeavyResourceRequiredForAllTests(
"@BeforeAll", "@AfterAll");
heavyResource.start(new DummyConfiguration());
}
@AfterAll
static void willBeDoneAfterAllTests() {
heavyResource.close();
}
@BeforeEach
void willBeDoneBeforeEachTest() {
testSetup = new TestSetup(
"@BeforeEach", "@AfterEach");
testSetup.prepare();
systemUnderTest = new SystemUnderTest();
}
@AfterEach
void willBeDoneAfterEachTest() {
testSetup.cleanUp();
}
@Test
void shouldReturnTheTruth() {
Fact result = systemUnderTest.returnTheTruth();
assertTrue(result.isTruth());
}
@Test
void shouldReturnTheLie() {
Fact result = systemUnderTest.returnTheLie();
assertFalse(result.isTruth());
}
}
Câteva remarci despre testul de mai sus:
Metoda adnotată cu @BeforeAll
va fi executată o singură dată, înaintea rulării testelor.
Metoda adnotată cu @BeforeEach
va fi executată de fiecare dată, înaintea execuţiei fiecărui test.
Metodele adnotate cu @Test
vor fi executate una câte una, pentru verificarea funcţionalităţii.
Metoda adnotată cu @AfterEach
va fi executată de fiecare dată după rularea unui test.
@AfterAll
va fi executată o singură data, după rularea tuturor testelor.JUnit Jupiter pune la dispoziţie mai multe metode de tip assertion decât JUnit 4. Au fost adăugate câteva care funcţionează foarte bine împreună cu expresiile lambda din Java 8. Toate metodele de acest fel din JUnit Jupiter sunt statice şi provin din clasa org.junit.jupiter.api.Assertions
.
Comparaţia între assertions în JUnit 4 şi JUnit 5 arată astfel:
Mesajul din assertions este ultimul parametru al metodelor:
static void assertX(..., String message)
static void assertX(..., Supplier messageSupplier)
Supplierul permite iniţializarea leneşă în cazul mesajelor complexe. O bucată de cod care să utilizeze posibile assertions arată astfel:
class AssertionsExample {
private final SystemUnderTest systemUnderTest =
new SystemUnderTest("Assertions");
@Test
void shouldRecognizeWhenTestsStarted() {
systemUnderTest.examine();
assertTrue(systemUnderTest.isUnderTest());
}
@Test
void shouldRecognizeIfTestsDidNotStarted() {
assertFalse(systemUnderTest.isUnderTest());
}
@Test
void shouldReturnNullInCaseOfNoJob() {
assertNull(systemUnderTest.getCurrentJob());
}
@Test
void shouldReturnJobIfThereIsOneRun() {
systemUnderTest.addJob(aSomeJob());
systemUnderTest.run();
assertNotNull(systemUnderTest.getCurrentJob());
}
@Test
void shouldRecognizeTheSameJob() {
Job job = aSomeJob();
systemUnderTest.addJob(job);
systemUnderTest.run();
assertSame(job, systemUnderTest.getCurrentJob());
}
@Test
void shouldRecognizeNotTheSameJob() {
systemUnderTest.addJob(aSomeJob());
systemUnderTest.run();
assertNotSame(aSomeJob(),
systemUnderTest.getCurrentJob());
}
@Test
void shouldRecognizeEqualJob() {
systemUnderTest.addJob(aSomeJob());
systemUnderTest.run();
assertEquals(aSomeJob(),
systemUnderTest.getCurrentJob());
}
@Test
void shouldRecognizeNotEqualJob() {
systemUnderTest.addJob(aSomeJob());
systemUnderTest.run();
assertNotEquals(aDifferentJob(),
systemUnderTest.getCurrentJob());
}
@Test
void shouldRecognizeEqualJobs() {
Object[] expectedJobs =
{aSomeJob(), aDifferentJob()};
systemUnderTest.addJob(aSomeJob());
systemUnderTest.addJob(aDifferentJob());
assertArrayEquals(expectedJobs,
systemUnderTest.getJobs());
}
private Job aDifferentJob() {
return aJob("different job");
}
private Job aSomeJob() {
return aJob("some job");
}
private Job aJob(String name) {
return new Job(name);
}
}
JUnit Jupiter pune la dispoziţie o parte din metodele de tip assumption din JUnit 4. JUnit Jupiter adaugă de asemenea, metode care funcţionează împreună cu expresiile lambda Java 8. Toate metodele de tip assumption din JUnit Jupiter sunt statice şi provin din clasa org.junit.jupiter.api.Assumptions. Parametrul mesaj este pe ultima poziţie.
Metodele de tip assertions sunt executate doar în cazul în care presupunerile de tip assumption sunt îndeplinite.
Metoda arată astfel:
static void assumingThat(
BooleanSupplier assumptionSupplier,
Executable executable)
O comparaţie între assumptions în JUnit 4 şi JUnit 5 se prezintă în această formă:
Utilizarea unei metode de tip assume poate arăta așa:
class AssumeExample {
private static final String
EXPECTED_JAVA_VERSION = "1.8";
private final TestsEnvironment
environment = new TestsEnvironment(
new JavaSpecification(
System.getProperty(
"java.vm.specification.version")),
new OperationSystem(
System.getProperty(
"os.name"), System.getProperty("os.arch"))
);
private final SystemUnderTest
systemUnderTest = new SystemUnderTest();
@BeforeEach
void init() {
assumeTrue(environment.isWindows());
}
@Test
void shouldRecognizeThatHasNoJobToRun() {
assumingThat(
() -> environment.aJavaVersion()
.equals(EXPECTED_JAVA_VERSION),
() -> assertFalse(
systemUnderTest.hasJobToRun()));
}
@Test
void shouldRecognizeThatHasJobToRun() {
assumeTrue(environment.isAmd64Architecture());
systemUnderTest.run(new Job());
assertTrue(systemUnderTest.hasJobToRun());
}
}
JUnit 5 oferă un API nou şi flexibil pentru scrierea de teste, pentru assertions şi assumptions. Sunt acordate o mulţime de metode statice şi adaptate noilor facilităţi de programare funcţională, introduse de Java 8. De asemenea, noua arhitectură modulară facilitează atât munca dezvoltatorului cât şi interacţiunea cu IDE-urile şi cu alte unelte. Cititorul ar putea avea în acest moment o primă imagine despre ceea ce JUnit 5 pune la dispoziţie şi poate începe scrierea unor prime teste folosind acest framework.