Testen in Java: Von Unit- bis zu End-to-End-Tests
Qualitätssicherung durch Tests ist die Grundlage zuverlässiger Software. In der Java-Welt gibt es eine etablierte Testpyramide mit drei Hauptebenen: Unit-Tests, Integrationstests und E2E-Tests (End-to-End). In diesem Artikel analysieren wir jede dieser Ebenen, geben praktische Beispiele in Java und erklären, wie Sie eine effektive Teststrategie für Ihr Projekt aufbauen.
1. Unit-Testing: Die Basis der Pyramide
Unit-Tests überprüfen einzelne Komponenten eines Programms in Isolation – in der Regel eine Klasse oder Methode. Sie sind schnell ausführbar, benötigen keine externen Abhängigkeiten (Datenbanken, APIs) und sollten den Großteil der Geschäftslogik abdecken.
Werkzeuge für Unit-Tests in Java
- JUnit 5 – das Standard-Framework zum Schreiben von Tests.
- Mockito – eine Bibliothek zum Erstellen von Mock-Objekten.
- AssertJ – Fluent-Assertions für lesbare Überprüfungen.
Beispiel eines Unit-Tests mit JUnit 5 und Mockito
Angenommen, wir haben einen Service OrderService, der von PaymentGateway abhängt. Wir möchten die Logik zur Rabattberechnung testen, ohne das echte Zahlungsgateway aufzurufen.
// Klasse zum Testenpublic class OrderService { private final PaymentGateway paymentGateway;
public OrderService(PaymentGateway paymentGateway) { this.paymentGateway = paymentGateway; }
public double calculateDiscount(double amount) { if (amount > 1000) { return amount * 0.1; // 10% Rabatt } return 0; }}
// Unit-Testimport org.junit.jupiter.api.Test;import org.junit.jupiter.api.extension.ExtendWith;import org.mockito.InjectMocks;import org.mockito.Mock;import org.mockito.junit.jupiter.MockitoExtension;import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(MockitoExtension.class)public class OrderServiceTest {
@Mock private PaymentGateway paymentGateway;
@InjectMocks private OrderService orderService;
@Test public void testCalculateDiscount_AmountAbove1000_Returns10Percent() { double result = orderService.calculateDiscount(1500); assertEquals(150.0, result, 0.001); }
@Test public void testCalculateDiscount_AmountBelow1000_ReturnsZero() { double result = orderService.calculateDiscount(500); assertEquals(0.0, result); }}Bewährte Praktiken: Verwenden Sie @BeforeEach zur Initialisierung gemeinsamer Daten, vermeiden Sie das direkte Testen privater Methoden und befolgen Sie die Regel "eine Überprüfung pro Test".
2. Integrationstests: Überprüfung der Interaktion
Integrationstests prüfen, wie mehrere Module zusammenarbeiten – zum Beispiel die Interaktion eines Service mit einer Datenbank, Message Queues oder externen APIs. Im Gegensatz zu Unit-Tests werden hier reale oder In-Memory-Abhängigkeiten verwendet.
Werkzeuge für Integrationstests
- Spring Boot Test – zum Testen von Spring-Anwendungen.
- Testcontainers – zum Hochfahren von Docker-Containern (Datenbanken, Kafka).
- H2 Database – In-Memory-Datenbank für schnelle Tests.
Beispiel eines Integrationstests mit Spring Boot
Wir testen einen REST-Controller mit einer echten Datenbankverbindung (im Test verwenden wir H2).
// Controller@RestController@RequestMapping("/api/users")public class UserController { private final UserRepository userRepository;
public UserController(UserRepository userRepository) { this.userRepository = userRepository; }
@GetMapping("/{id}") public User getUser(@PathVariable Long id) { return userRepository.findById(id) .orElseThrow(() -> new Runti