JUnit5 Handleiding

JUnit5 Zelfstudie

In deze JUnit-zelfstudie zullen we de basisprincipes van JUnit5 en de nieuwe functies ervan introduceren aan de hand van voorbeelden. In de Java-wereld is JUnit een van de populaire frameworks die wordt gebruikt om unit tests uit te voeren op Java-code. JUnit helpt ontwikkelaars voornamelijk om hun code zelf te testen op de JVM.

JUnit5 Architectuur

JUnit Platform

  • Start testframeworks op de JVM
  • Heeft TestEngine API die wordt gebruikt om een testframework te bouwen dat op het JUnit-platform draait

JUnit Jupiter

  • Combinatie van een nieuw programmeringsmodel voor het schrijven van tests en een uitbreidingsmodel voor extensies
  • Toevoeging van nieuwe annotaties zoals @BeforeEach, @AfterEach, @AfterAll, @BeforeAll etc.

JUnit Vintage

  • Biedt ondersteuning voor het uitvoeren van vorige JUnit-versie 3 en 4 tests op dit nieuwe platform

JUnit Maven Afhankelijkheden

Om JUnit5-gebaseerde testcases in een project te implementeren, voeg de volgende afhankelijkheid toe aan het pom.xml-bestand van het project:

  • JUnit 5 Bibliotheek
<dependency>
     <groupId>org.junit.jupiter</groupId>
     <artifactId>junit-jupiter-engine</artifactId>
     <version>5.1.1</version>
     <scope>test</scope>
</dependency>
<dependency>
     <groupId>org.junit.platform</groupId>
     <artifactId>junit-platform-runner</artifactId>
     <version> 1.1.1</version>
     <scope>test</scope>
</dependency>
  • JUnit5 maven surefire-provider om de unit tests uit te voeren waar de IDE geen JUnit5-ondersteuning heeft (als de IDE ondersteuning heeft, is dit punt niet nodig)
<plugin>
     <artifactId>maven-surefire-plugin</artifactId>
     <version>2.19.1</version>
     <dependencies>
          <dependency>
               <groupId>org.junit.platform</groupId>
               <artifactId>junit-platform-surefire-provider</artifactId>
               <version>1.0.2</version>
          </dependency>
     </dependencies>
</plugin>

JUnit5 Nieuwe Functies

Het vereist Java 8 of hoger tijdens runtime. Maar men kan nog steeds code testen die is gecompileerd met eerdere Java-versies. Er zijn verschillende nieuwe functies geïntroduceerd.

JUnit Aantekeningen

Hieronder staan enkele veelgebruikte annotaties die worden geleverd:

Annotation Description
@Test Denotes a test method
@DisplayName Declares a custom display name for the test class or test method
@BeforeEach Denotes that the annotated method should be executed before each test method
@AfterEach Denotes that the annotated method should be executed after each test method
@BeforeAll Denotes that the annotated method should be executed before all test methods
@AfterAll Denotes that the annotated method should be executed after all test methods
@Disable Used to disable a test class or test method
@Nested Denotes that the annotated class is a nested, non-static test class
@Tag Declare tags for filtering tests
@ExtendWith Register custom extensions
package com.journaldev;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class JUnit5Sample1Test {

  @BeforeAll
  static void beforeAll() {
    System.out.println("**--- Executed once before all test methods in this class ---**");
  }

  @BeforeEach
  void beforeEach() {
    System.out.println("**--- Executed before each test method in this class ---**");
  }

  @Test
  void testMethod1() {
    System.out.println("**--- Test method1 executed ---**");
  }

  @DisplayName("Test method2 with condition")
  @Test
  void testMethod2() {
    System.out.println("**--- Test method2 executed ---**");
  }

  @Test
  @Disabled("implementation pending")
  void testMethod3() {
	  System.out.println("**--- Test method3 executed ---**");
  }

  @AfterEach
  void afterEach() {
    System.out.println("**--- Executed after each test method in this class ---**");
  }

  @AfterAll
  static void afterAll() {
    System.out.println("**--- Executed once after all test methods in this class ---**");
  }


}

We kunnen de bovenstaande JUnit-testklasse uitvoeren in Eclipse -> Uitvoeren als -> JUnit-test.

JUnit Asserties

Elke testmethode moet worden geëvalueerd tegen een voorwaarde om waar te zijn met behulp van asserties, zodat de test kan doorgaan met uitvoeren. JUnit Jupiter-asserties worden bewaard in de org.junit.jupiter.api.Assertions-klasse. Alle methoden zijn statisch.

Assertion Description
assertEquals(expected, actual) Fails when expected does not equal actual
assertFalse(expression) Fails when expression is not false
assertNull(actual) Fails when actual is not null
assertNotNull(actual) Fails when actual is null
assertAll() Group many assertions and every assertion is executed even if one or more of them fails
assertTrue(expression) Fails if expression is not true
assertThrows() Class to be tested is expected to throw an exception
@Test
void testAssertEqual() {
	 assertEquals("ABC", "ABC");
	 assertEquals(20, 20, "optional assertion message");
	 assertEquals(2 + 2, 4);
}

@Test
void testAssertFalse() {
	 assertFalse("FirstName".length() == 10);
	 assertFalse(10 > 20, "assertion message");
}

@Test
void testAssertNull() {
     String str1 = null;
	 String str2 = "abc";
	 assertNull(str1);
	 assertNotNull(str2);	
}

@Test
void testAssertAll() {
	 String str1 = "abc";
	 String str2 = "pqr";
	 String str3 = "xyz";
	 assertAll("numbers",
	      () -> assertEquals(str1,"abc"),
		  () -> assertEquals(str2,"pqr"),
		  () -> assertEquals(str3,"xyz")
	 );
	 //de onderstaande code uitcommentariëren en elke assertie-uitvoering begrijpen
     /*assertAll("numbers",
		  () -> assertEquals(str1,"abc"),
		  () -> assertEquals(str2,"pqr1"),
		  () -> assertEquals(str3,"xyz1")
	 );*/
}

@Test
void testAssertTrue() {
	 assertTrue("FirstName".startsWith("F"));
	 assertTrue(10  {
	      throw new IllegalArgumentException("Illegal Argument Exception occured");
	 });
	 assertEquals("Illegal Argument Exception occured", exception.getMessage());
}

JUnit5 Imports

Zijn testklassen hebben de importverklaring org.junit.jupiter.api.Test nodig en niet org.junit.Test. Ook hoeven de testmethoden niet openbaar te zijn en kunnen ze zich in de lokale pakketruimte bevinden.

import org.junit.jupiter.api.Test;

JUnit5 Aannames

Aannames zijn statische methoden in de org.junit.jupiter.api.Assumptions-klasse. Ze voeren een test alleen uit wanneer de gespecificeerde voorwaarde is voldaan, anders wordt de test afgebroken. De afgebroken test zal geen buildfout veroorzaken. Wanneer een aanname faalt, wordt org.opentest4j.TestAbortedException gegooid en wordt de test overgeslagen.

Assumptions Description
assumeTrue Execute the body of lamda when the positive condition hold else test will be skipped
assumeFalse Execute the body of lamda when the negative condition hold else test will be skipped
assumingThat Portion of the test method will execute if an assumption holds true and everything after the lambda will execute irrespective of the assumption in assumingThat() holds
@Test
void testAssumeTrue() {
     boolean b = 'A' == 'A';
     assumeTrue(b);
     assertEquals("Hello", "Hello");
}

@Test
@DisplayName("test executes only on Saturday")
public void testAssumeTrueSaturday() {
     LocalDateTime dt = LocalDateTime.now();
     assumeTrue(dt.getDayOfWeek().getValue() == 6);
     System.out.println("further code will execute only if above assumption holds true");
}

@Test
void testAssumeFalse() {
     boolean b = 'A' != 'A';
     assumeFalse(b);
     assertEquals("Hello", "Hello");
}

@Test
void testAssumeFalseEnvProp() {
     System.setProperty("env", "prod");
     assumeFalse("dev".equals(System.getProperty("env")));
     System.out.println("further code will execute only if above assumption hold");
}

@Test
void testAssumingThat() {
     System.setProperty("env", "test");
     assumingThat("test".equals(System.getProperty("env")),
          () -> {
               assertEquals(10, 10);
               System.out.println("perform below assertions only on the test env");
               });

     assertEquals(20, 20);
     System.out.println("perform below assertions on all env");
}

Geneste JUnit-testklassen

Geneste tests maken het mogelijk om geneste klassen te maken en al haar testmethoden uit te voeren. De binnenklassen moeten niet-statisch zijn. Markeer gewoon de binnenklassen met @Genest en alle testmethoden erin zullen worden uitgevoerd.

@BeforeAll
static void beforeAll() {
     System.out.println("**--- JUnit5Sample4Test :: beforeAll :: Executed once before all test methods ---**");
}
 
@BeforeEach
void beforeEach() {
	 System.out.println("**--- JUnit5Sample4Test :: beforeEach :: Executed before each test method ---**");
}

@AfterEach
void afterEach() {
	 System.out.println("**--- JUnit5Sample4Test :: afterEach :: Executed after each test method ---**");
}

@AfterAll
static void afterAll() {
	 System.out.println("**--- JUnit5Sample4Test :: afterAll :: Executed after all test method ---**");
}
 
     @Nested
     class InnerClass {
 
          @BeforeEach
          void beforeEach() {
               System.out.println("**--- InnerClass :: beforeEach :: Executed before each test method ---**");
          }
 
          @AfterEach
          void afterEach() {
        	   System.out.println("**--- InnerClass :: afterEach :: Executed after each test method ---**");
          }
 
          @Test
          void testMethod1() {
        	   System.out.println("**--- InnerClass :: testMethod1 :: Executed test method1 ---**");
          }
 
          @Nested
          class InnerMostClass {
 
               @BeforeEach
               void beforeEach() {
                    System.out.println("**--- InnerMostClass :: beforeEach :: Executed before each test method ---**");
               }
 
               @AfterEach
               void afterEach() {
            	    System.out.println("**--- InnerMostClass :: afterEach :: Executed after each test method ---**");
               }
 
               @Test
               void testMethod2() {
            	    System.out.println("**--- InnerMostClass :: testMethod2 :: Executed test method2 ---**");
               }
        }
    }

JUnit-testuitzondering

Er zijn situaties waarin wordt verwacht dat methoden een uitzondering gooien onder een specifieke voorwaarde. assertThrows zal de test laten mislukken als de gegeven methode de gespecificeerde uitzondering niet gooit.

Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
     throw new IllegalArgumentException("Illegal Argument Exception occured");
});
assertEquals("Illegal Argument Exception occured", exception.getMessage());

JUnit Testuitvoering

De unit tests kunnen op verschillende manieren worden uitgevoerd, twee van de manieren zijn als volgt:

  • Gebruik Eclipse IDE Oxygen.3a (4.7.3a) Release en open het te testen bestand. Klik met de rechtermuisknop op het bestand en kies de optie ‘Uitvoeren Als’ gevolgd door ‘JUnit-test’
  • Gebruik het commando mvn test in de Windows-opdrachtprompt

Samenvatting

We hebben JUnit5 en zijn nieuwe functies verkend met enkele voorbeelden. We hebben ook gekeken hoe we JUnit-annotaties, assertions, aannames, uitzonderingen kunnen gebruiken en geneste testklassen kunnen schrijven.

U kunt het volledige voorbeeldproject downloaden van onze GitHub Repository.

Source:
https://www.digitalocean.com/community/tutorials/junit5-tutorial