Vodič za JUnit 5, 1. dio: Jedinstveno testiranje s JUnit 5, Mockito i Hamcrest

JUnit 5 novi je de facto standard za razvoj jediničnih testova u Javi. Ova najnovija verzija ostavila je ograničenja Java 5 i integrirala mnoge značajke Java 8, a posebno podršku za lambda izraze.

U ovoj prvoj polovici dvodijelnog uvoda u JUnit 5 započet ćete s testiranjem s JUnit 5. Pokazat ću vam kako konfigurirati Maven projekt da koristi JUnit 5, kako pisati testove pomoću i @Testi @ParameterizedTestnapomena, i kako raditi s novim bilješkama životnog ciklusa u JUnit 5. Vidjet ćete i kratki primjer korištenja oznaka filtra, a ja ću vam pokazati kako integrirati JUnit 5 s bibliotekom tvrdnji treće strane - u ovom slučaju Hamcrestom . Konačno, dobit ćete brzi uvod u integraciju JUnit 5 s Mockitom, tako da možete pisati robusnije jedinice testove za složene sustave iz stvarnog svijeta.

preuzimanje Preuzmite kod Preuzmite izvorni kod za primjere u ovom vodiču. Stvorio Steven Haines za JavaWorld.

Razvoj vođen testom

Ako ste bilo koje vrijeme razvijali Java kôd, vjerojatno ste dobro upoznati s testnim razvojem, tako da ću ovaj odjeljak biti kratak. Važno je razumjeti zašto , međutim, pišemo unit testove, kao i strategije koje programeri primjenjuju prilikom dizajniranja unit testova.

Test-driven development (TDD) postupak je razvoja softvera koji isprepliće kodiranje, testiranje i dizajn. To je test test pristup kojem je cilj poboljšati kvalitetu vaših aplikacija. Razvoj vođen testom definiran je sljedećim životnim ciklusom:

  1. Dodajte test.
  2. Pokrenite sve testove i primijetite kako novi test ne uspijeva.
  3. Primijenite kod.
  4. Izvršite sve testove i promatrajte kako novi test uspijeva.
  5. Refaktorirajte kod.

Slika 1 prikazuje ovaj TDD životni ciklus.

Steven Haines

Dvostruka je svrha pisanja testova prije pisanja koda. Prvo, prisiljava vas na razmišljanje o poslovnom problemu koji pokušavate riješiti. Na primjer, kako bi se trebali ponašati uspješni scenariji? Koji bi uvjeti trebali propasti? Kako bi trebali propasti? Drugo, prvo vam testiranje daje više povjerenja u vaše testove. Kad god napišem testove nakon pisanja koda, uvijek ih moram razbiti kako bih osigurao da zapravo hvataju pogreške. Pisanje testova prvo izbjegava ovaj dodatni korak.

Pisanje testova za sretan put obično je lako: s obzirom na dobar unos, klasa bi trebala vratiti deterministički odgovor. Ali pisanje negativnih (ili neuspješnih) testnih slučajeva, posebno za složene komponente, može biti složenije.

Kao primjer, razmislite o pisanju testova za spremište baze podataka. Na sretnom putu umetnemo zapis u bazu podataka i primamo natrag stvoreni objekt, uključujući sve generirane ključeve. U stvarnosti moramo razmotriti i mogućnost sukoba, poput umetanja zapisa s jedinstvenom vrijednošću stupca koji se već nalazi u drugom zapisu. Uz to, što se događa kada se spremište ne može povezati s bazom podataka, možda zato što su se promijenili korisničko ime ili lozinka? Što se događa ako u prijevozu dođe do mrežne pogreške? Što se događa ako zahtjev ne završi u vašem definiranom ograničenju?

Da biste izgradili robusnu komponentu, morate razmotriti sve vjerojatne i malo vjerojatne scenarije, razviti testove za njih i napisati svoj kôd kako bi zadovoljili te testove. Dalje u članku pogledat ćemo strategije za stvaranje različitih scenarija neuspjeha, zajedno s nekim novim značajkama u JUnit 5 koje vam mogu pomoći u testiranju tih scenarija.

Usvajanje JUnit-a 5

Ako već neko vrijeme koristite JUnit, neke promjene u JUnit 5 bit će prilagodba. Evo sažetka na visokoj razini onoga što se razlikuje između dvije verzije:

  • JUnit 5 je sada upakiran u org.junit.jupitergrupu, što mijenja način na koji ćete ga uključiti u svoje projekte Maven i Gradle.
  • JUnit 4 zahtijevao je najmanje JDK od JDK 5; JUnit 5 zahtijeva najmanje JDK 8.
  • JUnit 4-a @Before, @BeforeClass, @After, i @AfterClassbilješke su zamijenjene @BeforeEach, @BeforeAll, @AfterEachi @AfterAll, respektivno.
  • Bilješka JUnit 4 @Ignorezamijenjena je @Disablednapomenom.
  • @CategoryPrimjedba je zamijenjen @Tagkomentaru.
  • JUnit 5 dodaje novi skup metoda tvrdnji.
  • Pokretači su zamijenjeni proširenjima, s novim API-jem za implementatore proširenja.
  • JUnit 5 uvodi pretpostavke koje zaustavljaju provođenje testa.
  • JUnit 5 podržava ugniježđene i dinamičke klase ispitivanja.

Većinu ovih novih značajki istražit ćemo u ovom članku.

Jedinstveno testiranje s JUnit 5

Počnimo jednostavno, s primjera od kraja do kraja konfiguriranja projekta za korištenje JUnit 5 za jedinični test. Popis 1 prikazuje MathToolsklasu čija metoda pretvara brojnik i nazivnik u a double.

Popis 1. Primjer JUnit 5 projekta (MathTools.java)

 package com.javaworld.geekcap.math; public class MathTools { public static double convertToDecimal(int numerator, int denominator) { if (denominator == 0) { throw new IllegalArgumentException("Denominator must not be 0"); } return (double)numerator / (double)denominator; } }

Imamo dva osnovna scenarija za testiranje MathToolsklase i njene metode:

  • Vrijedi Test , u kojemu ćemo proći bez nula cijelih brojeva za brojnik i nazivnik.
  • Neuspjeh scenarij , u kojem prolazimo nula vrijednost za nazivnik.

Popis 2 prikazuje klasu testa JUnit 5 za testiranje ova dva scenarija.

Popis 2. Probna klasa JUnit 5 (MathToolsTest.java)

 package com.javaworld.geekcap.math; import java.lang.IllegalArgumentException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class MathToolsTest { @Test void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.75, result); } @Test void testConvertToDecimalInvalidDenominator() { Assertions.assertThrows(IllegalArgumentException.class, () -> MathTools.convertToDecimal(3, 0)); } }

Na popisu 2, testConvertToDecimalInvalidDenominatormetoda izvršava MathTools::convertToDecimalmetodu unutar assertThrowspoziva. Prvi je argument očekivana vrsta iznimke koja će se izbaciti. Drugi argument je funkcija koja će izbaciti tu iznimku. assertThrowsMetoda izvršava funkciju i potvrđuje da je očekivani tip izuzetak je bačena.

Klasa Assertions i njegove metode

org.junit.jupiter.api.TestZapažanje označava testu. Imajte na umu da @Testnapomena sada dolazi iz JUnit 5 Jupiter API paketa umjesto iz JUnit 4 org.junitpaketa. testConvertToDecimalSuccessMetoda prvo izvršava MathTools::convertToDecimalpostupak s nazivniku 3 i nazivnik 4, tada tvrdi da je rezultat jednak 0,75. Predavanje org.junit.jupiter.api.Assertionsnudi skup staticmetoda za usporedbu stvarnih i očekivanih rezultata. AssertionsKlasa ima sljedeće metode koje pokrivaju većinu primitivnih tipova podataka:

  • assertArrayEquals uspoređuje sadržaj stvarnog niza s očekivanim nizom.
  • assertEquals uspoređuje stvarnu vrijednost s očekivanom vrijednošću.
  • assertNotEquals uspoređuje dvije vrijednosti kako bi potvrdila da nisu jednake.
  • assertTrue provjerava je li navedena vrijednost istinita.
  • assertFalse potvrđuje da je navedena vrijednost lažna.
  • assertLinesMatchuspoređuje dva popisa Strings.
  • assertNull validates that the provided value is null.
  • assertNotNull validates that the provided value is not null.
  • assertSame validates that two values reference the same object.
  • assertNotSame validates that two values do not reference the same object.
  • assertThrows validates that the execution of a method throws an expected exception (you can see this in the testConvertToDecimalInvalidDenominator example above).
  • assertTimeout validates that a supplied function completes within a specified timeout.
  • assertTimeoutPreemptively validates that a supplied function completes within a specified timeout, but once the timeout is reached it kills the function's execution.

If any of these assertion methods fail, the unit test is marked as failed. That failure notice will be written to the screen when you run the test, then saved in a report file.

Using delta with assertEquals

When using float and double values in an assertEquals, you can also specify a delta that represents a threshold of difference between the two. In our example we could have added a delta of 0.001, in case 0.75 was actually returned as 0.750001.

Analyzing your test results

In addition to validating a value or behavior, the assert methods can also accept a textual description of the error, which can help you diagnose failures. For example:

 Assertions.assertEquals(0.75, result, "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); Assertions.assertEquals(0.75, result, () -> "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); 

The output will show the expected value of 0.75 and the actual value. It will also display the specified message, which can help you understand the context of the error. The difference between the two variations is that the first one always creates the message, even if it is not displayed, whereas the second one only constructs the message if the assertion fails. In this case, the construction of the message is trivial, so it doesn't really matter. Still, there is no need to construct an error message for a test that passes, so it's usually a best practice to use the second style.

Finally, if you're using an IDE like IntelliJ to run your tests, each test method will be displayed by its method name. This is fine if your method names are readable, but you can also add a @DisplayName annotation to your test methods to better identify the tests:

@Test @DisplayName("Test successful decimal conversion") void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.751, result); }

Running your unit test

In order to run JUnit 5 tests from a Maven project, you need to include the maven-surefire-plugin in the Maven pom.xml file and add a new dependency. Listing 3 shows the pom.xml file for this project.

Listing 3. Maven pom.xml for an example JUnit 5 project

  4.0.0 com.javaworld.geekcap junit5 jar 1.0-SNAPSHOT    org.apache.maven.plugins maven-compiler-plugin 3.8.1  8 8    org.apache.maven.plugins maven-surefire-plugin 3.0.0-M4    junit5 //maven.apache.org   org.junit.jupiter junit-jupiter 5.6.0 test   

JUnit 5 dependencies

JUnit 5 packages its components in the org.junit.jupiter group and we need to add the junit-jupiter artifact, which is an aggregator artifact that imports the following dependencies:

  • junit-jupiter-api defines the API for writing tests and extensions.
  • junit-jupiter-engine je implementacija testnog motora koji pokreće jedinična ispitivanja.
  • junit-jupiter-params pruža podršku za parametrizirana ispitivanja.

Dalje, moramo dodati dodatak za maven-surefire-pluginizgradnju kako bismo mogli pokretati testove.

Konačno, obavezno uključite maven-compiler-plugini verziju Java 8 ili noviju, tako da ćete moći koristiti značajke Java 8 poput lambda.

Trči!

Upotrijebite sljedeću naredbu za pokretanje klase testa iz vašeg IDE-a ili iz Mavena:

mvn clean test

Ako ste uspješni, trebali biste vidjeti izlaz sličan sljedećem:

 [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.javaworld.geekcap.math.MathToolsTest [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.04 s - in com.javaworld.geekcap.math.MathToolsTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.832 s [INFO] Finished at: 2020-02-16T08:21:15-05:00 [INFO] ------------------------------------------------------------------------