Testen in Java: Unit, Integration, E2E auf fortgeschrittenem Niveau

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Testen in Java: Unit, Integration, E2E — Fortgeschrittenes Niveau



In der modernen Java-Entwicklung ist Testen nicht nur eine Formalität, sondern eine grundlegende Praxis, die Stabilität und Vorhersagbarkeit des Codes gewährleistet. Einfache Tests wie @Test mit Assert.assertEquals sind längst komplexen, mehrschichtigen Strategien gewichen. In diesem Artikel analysieren wir fortgeschrittene Techniken für jede Ebene der Testpyramide: Unit, Integration und End-to-End (E2E). Sie erfahren, wie Sie wartbare, schnelle und zuverlässige Tests schreiben, die Ihren Code in eine gut abgestimmte Maschine verwandeln.



1. Fortgeschrittene Unit-Tests: Mocks, Stubs und Parametrisierung



Unit-Tests überprüfen ein isoliertes Stück Code (normalerweise eine Klasse oder Methode). Auf fortgeschrittenem Niveau bewegen wir uns von einfachen Szenarien zu komplexen Interaktionen und Grenzfällen. Das wichtigste Werkzeug hierfür ist die Bibliothek Mockito.



1.1. Verwendung von ArgumentCaptor und komplexen Antworten (Answer)



Oft ist es notwendig, nicht nur zu überprüfen, ob eine Methode aufgerufen wurde, sondern auch welche Argumente ihr übergeben wurden. Dafür wird ArgumentCaptor verwendet.



// Beispiel: Überprüfung, ob die richtige Nachricht an den Dienst gesendet wurde@ExtendWith(MockitoExtension.class)public class NotificationServiceTest {    @Mock    private EmailSender emailSender;    @InjectMocks    private NotificationService notificationService;

@Test void shouldSendEmailWithCorrectContent() { // Arrange User user = new User("test@example.com", "Test User"); // Act notificationService.sendWelcomeEmail(user); // Assert ArgumentCaptor<EmailMessage> captor = ArgumentCaptor.forClass(EmailMessage.class); verify(emailSender).send(captor.capture()); EmailMessage capturedMessage = captor.getValue(); assertEquals("test@example.com", capturedMessage.getTo()); assertTrue(capturedMessage.getBody().contains("Test User")); }}


Für die dynamische Erstellung von Antworten basierend auf Eingabedaten verwenden Sie das Interface Answer:



@Testvoid testDynamicResponse() {    when(dataRepository.findById(anyLong())).thenAnswer(invocation -> {        Long id = invocation.getArgument(0);        return new DataEntity(id, "Entity with ID: " + id);    });    assertEquals("Entity with ID: 5", dataService.getDataById(5L).getName());}


1.2. Parametrisierte Tests (JUnit 5)



Parametrisierung ermöglicht es, denselben Test mit verschiedenen Datensätzen auszuführen und so Code-Duplizierung zu vermeiden. JUnit 5 bietet leistungsstarke Annotationen wie @CsvSource und @MethodSource.



@ParameterizedTest@CsvSource({    "10, 20, 30",    "0, 0, 0",    "-5, 5, 0"})void testSum(int a, int b, int expectedSum) {    Calculator calculator = new Calculator();    assertEquals(expectedSum, calculator.add(a, b));}


2. Integrationstests: Arbeiten mit Datenbanken und externen Diensten



Integrationstests überprüfen die Interaktion zwischen Komponenten des Systems, z. B. mit einer echten Datenbank oder Warteschlangen. Die größte Herausforderung ist die Verwaltung des Umgebungszustands. Hier kommt Testcontainers zur Hilfe.



2.1. Testcontainers für isolierte Datenbanken



Testcontainers startet Docker-Container mit der erforderlichen Software (PostgreSQL, Redis, Kafka) direkt während der Tests und gewährleistet so Sauberkeit und Reproduzierbarkeit.



@SpringBootTest@Testcontainersclass UserRepositoryIntegrationTest {

@Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15") .withDatabaseName("testdb") .withUsername("test") .withPassword("test");

@DynamicPropertySource static void configureProperties(DynamicPropertyR

Blogs

Buchempfehlungen