Testing-Time-Travel-Serie
Von Dr. Cassian Holt & Jamal Hassan
Java Fleet Systems Consulting, Essen-Rüttenscheid
🔗 Bisher in der Serie
- Prolog – Warum diese Serie anders wird (Cassian & Jamal stellen sich vor)
- Teil 1: Unit Testing Grundlagen – Jamals Production-Disaster und AAA-Pattern 🔧
- Teil 2: Die Test-Pyramide – 70/20/10 und warum das wichtig ist 👥
- Teil 3: TDD & Property-Based Testing – Kontroverse Diskussion über Test-Driven Development 👥
- Teil 4: Mocking & Test Doubles – Die Kunst der Fake-Objekte 🔧
Heute: Teil 5 – Security-Testing & Chaos Engineering 👥
⚡ 30-Sekunden-Zusammenfassung
Security-Testing:
- OWASP Top 10 gegen deine REST-API testen
- Authentication & Authorization richtig absichern
- Property-Based Security Testing für gefährliche Inputs
Chaos Engineering:
- Circuit Breaker für externe Services
- Was passiert, wenn die Datenbank lahmlegt?
- Resilience unter High-Load
Bottom Line: Deine App funktioniert schön in der Entwicklung. Aber überlebt sie einen Angriff? Oder einen Service-Ausfall? Wir zeigen dir, wie du das testest.
👥 Hi, Cassian & Jamal hier!
Cassian: Heute wird’s ernst. Bisher haben wir über Unit-Tests, Mocking und TDD gesprochen. Alles wichtig. Aber jetzt kommt die Frage: Ist deine App Production-ready?
Jamal: Und damit meinen wir nicht nur „funktioniert die App?“, sondern: „Was passiert, wenn jemand versucht, sie zu hacken? Was passiert, wenn der Email-Service down geht? Bleibt die App trotzdem stabil?“
Cassian: Genau. Security und Resilience. Zwei Themen, die oft vergessen werden, bis es zu spät ist.
Jamal: Ich hatte das selbst. 2022, kurz bevor ich zu Java Fleet kam. Unser Startup hatte eine kleine E-Commerce-App. Lief super in der Entwicklung. Dann, drei Tage nach Launch: Payment-Service fällt aus. Unsere komplette App? Tot. Kein einziger Checkout mehr möglich. Wir haben 15.000€ Umsatz in 4 Stunden verloren.
Cassian: Und das ist der Punkt: Testing ist nicht nur Unit-Tests. Es geht auch darum zu testen, wie deine App unter Stress reagiert. Wie sie sich bei Angriffen verhält. Wie sie degradiert, wenn externe Services versagen.
Jamal: Also: Heute gibt’s zwei große Themen. Ich übernehme den Security-Teil, weil ich da die praktische Erfahrung habe. Cassian erklärt dir Chaos Engineering mit der üblichen wissenschaftlichen Tiefe.
Cassian: Fair enough. Aber ich verspreche, es praxisnah zu halten. Los geht’s!
🔒 Security-Testing: OWASP Top 10 in der Praxis
Jamal hier – Mein Security-Weckruf
Moin! Jamal hier. Security-Testing war für mich lange… naja, „nice to have“. Bis zu diesem verdammten Dienstag im März 2022.
Die Story:
Unsere E-Commerce-App. Live seit 3 Tagen. Läuft gut. Dann ruft mich um 14:30 Uhr mein Kollegen an: „Jamal, schau mal in die Logs. Da ist was komisch.“
Ich öffne die Logs. Und sehe das hier:
GET /api/products/search?query=' OR '1'='1 GET /api/products/search?query='; DROP TABLE products; -- GET /api/users/123 (User-ID von jemand anderem) GET /api/users/124 GET /api/users/125
Jemand scannt unsere API systematisch ab. SQL-Injection-Versuche. Broken Access Control Tests. Ein kompletter OWASP-Top-10-Angriff.
Das Schlimme: Einige Angriffe funktionierten. User 123 konnte die Daten von User 124 sehen. Unsere Produkt-Suche war anfällig für SQL-Injection.
Der Schaden: 3 Tage Notfall-Patching. DSGVO-Meldung (weil User-Daten exponiert waren). Vertrauensverlust bei Kunden. Und das Gefühl: „Hätten wir das nicht vorher testen können?“
Ja. Hätten wir.
Was ist Security-Testing?
Security-Testing bedeutet: Du testest nicht nur, ob deine App funktioniert, sondern wie sicher sie funktioniert.
Die drei Kern-Fragen:
- Können Angreifer auf Daten zugreifen, die ihnen nicht gehören? (Broken Access Control)
- Können Angreifer bösartigen Code in meine App einschleusen? (Injection Attacks)
- Was passiert, wenn jemand meine App missbraucht? (Rate Limiting, DoS)
OWASP Top 10 – Dein Security-Testplan
OWASP (Open Web Application Security Project) veröffentlicht alle paar Jahre die „Top 10“ der häufigsten Web-Security-Probleme. Die aktuelle Version ist von 2021 (Update 2023 in Arbeit).
Ich zeige dir jetzt, wie du die wichtigsten davon testest.
Test 1: Broken Access Control (OWASP A01)
Das Problem: User A kann auf Daten von User B zugreifen.
Praktisches Beispiel:
// ❌ GEFÄHRLICH - Keine Access-Control
@GetMapping("/api/tasks/{id}")
public Task getTask(@PathVariable Long id) {
return taskRepository.findById(id)
.orElseThrow(() -> new TaskNotFoundException(id));
}
// ✅ SICHER - Mit Access-Control
@GetMapping("/api/tasks/{id}")
public Task getTask(@PathVariable Long id, Principal principal) {
Task task = taskRepository.findById(id)
.orElseThrow(() -> new TaskNotFoundException(id));
// Prüfen: Gehört der Task dem aktuellen User?
if (!task.getUserId().equals(getCurrentUserId(principal))) {
throw new AccessDeniedException("Not your task!");
}
return task;
}
So testest du das:
@SpringBootTest
@AutoConfigureMockMvc
class TaskSecurityTest {
@Autowired
private MockMvc mockMvc;
@Test
@WithMockUser(username = "alice", roles = "USER")
@DisplayName("Alice darf NICHT auf Bobs Tasks zugreifen")
void shouldPreventCrossUserAccess() throws Exception {
// Arrange: Bob hat einen Task erstellt
Long bobsTaskId = createTaskForUser("bob", "Geheimes Projekt");
// Act & Assert: Alice versucht, Bobs Task zu lesen
mockMvc.perform(get("/api/tasks/" + bobsTaskId))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.error").value("Access denied"));
// Alice versucht, Bobs Task zu löschen
mockMvc.perform(delete("/api/tasks/" + bobsTaskId))
.andExpect(status().isForbidden());
// Alice versucht, Bobs Task zu ändern
mockMvc.perform(put("/api/tasks/" + bobsTaskId)
.contentType(MediaType.APPLICATION_JSON)
.content("{\"title\":\"Gehackt!\"}"))
.andExpect(status().isForbidden());
}
}
Meine Faustregel:
Jeder Endpoint, der eine Ressourcen-ID akzeptiert (/api/tasks/{id}), braucht einen Access-Control-Test. Immer.
Test 2: SQL Injection (OWASP A03)
Das Problem: Angreifer können SQL-Befehle in deine Queries einschleusen.
Praktisches Beispiel:
// ❌ GEFÄHRLICH - String Concatenation
@GetMapping("/api/tasks/search")
public List<Task> searchTasks(@RequestParam String query) {
String sql = "SELECT * FROM tasks WHERE title LIKE '%" + query + "%'";
return jdbcTemplate.query(sql, taskRowMapper);
}
// Bei query="'; DROP TABLE tasks; --" → SQL Injection!
// ✅ SICHER - Prepared Statements
@GetMapping("/api/tasks/search")
public List<Task> searchTasks(@RequestParam String query) {
// Spring Data JPA macht das automatisch
return taskRepository.findByTitleContaining(query);
}
So testest du das:
@Test
@WithMockUser(username = "test", roles = "USER")
@DisplayName("SQL Injection sollte blockiert werden")
void shouldPreventSqlInjection() throws Exception {
// Arrange: Normale Tasks existieren
createTask("Einkaufen");
createTask("Projekt abschließen");
// Act & Assert: SQL-Injection-Versuche
List<String> maliciousQueries = List.of(
"'; DROP TABLE tasks; --",
"' OR '1'='1",
"'; INSERT INTO tasks (title) VALUES ('HACKED'); --"
);
for (String malicious : maliciousQueries) {
mockMvc.perform(get("/api/tasks/search")
.param("query", malicious))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray())
// Die malicious query findet keine Tasks (oder wird validiert)
.andExpect(jsonPath("$[*].title").value(not(hasItem("HACKED"))));
}
// Paranoid Check: Tabelle existiert noch
assertThat(taskRepository.count()).isGreaterThan(0);
}
Test 3: XSS (Cross-Site Scripting)
Das Problem: Angreifer können JavaScript-Code in deine App einschleusen.
// ❌ GEFÄHRLICH - Kein Sanitizing
@PostMapping("/api/tasks")
public Task createTask(@RequestBody CreateTaskRequest request) {
Task task = new Task();
task.setTitle(request.getTitle()); // Was wenn title="<script>alert('XSS')</script>"?
return taskRepository.save(task);
}
// ✅ BESSER - Input Validation
@PostMapping("/api/tasks")
public Task createTask(@Valid @RequestBody CreateTaskRequest request) {
// @Valid triggert Bean Validation
Task task = new Task();
task.setTitle(sanitize(request.getTitle()));
return taskRepository.save(task);
}
Property-Based Testing für XSS:
@Property
@DisplayName("Alle Task-Titel müssen XSS-sicher sein")
void taskTitlesShouldBeXssSafe(@ForAll("xssPayloads") String xssTitle) {
// Arrange
CreateTaskRequest request = new CreateTaskRequest();
request.setTitle(xssTitle);
request.setDescription("Harmlose Beschreibung");
// Act
if (validator.isValid(request)) {
Task task = taskService.createTask(request);
// Assert: Gefährliche Zeichen wurden entfernt
assertThat(task.getTitle())
.doesNotContain("<script")
.doesNotContain("javascript:")
.doesNotContain("onerror=")
.doesNotMatch(".*[<>\"'].*");
}
}
@Provide
Arbitrary<String> xssPayloads() {
return Arbitraries.oneOf(
Arbitraries.just("<script>alert('XSS')</script>"),
Arbitraries.just("<img src=x onerror=alert('XSS')>"),
Arbitraries.just("javascript:alert('XSS')"),
Arbitraries.just("<script>alert('XSS')</script>")
);
}
Jamal’s Takeaway: Diese drei (Broken Access Control, SQL Injection, XSS) sind die häufigsten Probleme. Wenn du nur drei Security-Tests schreibst, dann diese.
<details> <summary>📚 <strong>Cassian’s Deep-Dive: Die Wissenschaft hinter Security-Testing</strong></summary>
Cassian hier.
Security-Testing basiert auf einem fundamentalen Prinzip aus der Informatik: Formal Verification vs. Empirical Testing.
Formal Verification würde bedeuten: Wir beweisen mathematisch, dass unsere App sicher ist. Das ist für reale Webanwendungen praktisch unmöglich (Halting Problem, State Space Explosion).
Empirical Testing bedeutet: Wir testen die häufigsten bekannten Angriffsmuster. OWASP Top 10 ist im Grunde eine „empirische Datenbank“ der häufigsten Schwachstellen.
Property-Based Testing ist hier besonders mächtig, weil es die Lücke schließt: Wir definieren Sicherheits-Properties („kein Input darf XSS ermöglichen“) und lassen das Framework hunderte Testfälle generieren.
Historischer Kontext: Die erste dokumentierte SQL-Injection war 1998. Seitdem: über 25 Jahre, und es ist immer noch OWASP #3. Warum? Weil Entwickler denselben Fehler (String Concatenation) immer wieder machen. Tests würden das sofort finden. </details>
🌪️ Chaos Engineering: Wenn alles schief geht
Cassian hier – Die Wissenschaft des kontrollierten Chaos
Cassian hier.
Jetzt wird’s spannend. Chaos Engineering ist eine Disziplin, die bei Netflix entstanden ist. Die Grundidee: Wir zerstören absichtlich Teile unseres Systems, um zu sehen, ob der Rest überlebt.
Das klingt verrückt. Aber es ist wissenschaftlich fundiert.
Die Theorie: Komplexe Systeme (und jede moderne Microservice-Architektur ist komplex) haben emergente Eigenschaften. Du kannst nicht vorhersagen, wie sich das Gesamtsystem verhält, nur weil du die einzelnen Komponenten verstehst.
Die Lösung: Empirisches Testen unter realistischen Bedingungen. Und realistische Bedingungen bedeuten: Dinge gehen schief.
Jamal: Cassian, das klingt wieder sehr theoretisch. Lass mich das übersetzen.
Jamal’s Praxis-Übersetzung: Was ist Chaos Engineering?
Jamal hier.
Chaos Engineering bedeutet: Du simulierst absichtlich Probleme in deinem System, um zu sehen, ob es überlebt.
Praktische Beispiele:
- Dein Email-Service ist down. Kann die App trotzdem Tasks erstellen?
- Deine Datenbank antwortet langsam. Bleibt die App responsiv?
- Dein Payment-Service ist überlastet. Werden Bestellungen trotzdem gespeichert?
Warum ist das wichtig?
Weil in Production IMMER etwas schief geht. Services fallen aus. Datenbanken werden langsam. APIs haben Timeouts. Die Frage ist nicht „ob“, sondern „wann“.
Pattern 1: Circuit Breaker
Das Problem: Dein Email-Service ist down. Jeder Aufruf wartet 30 Sekunden auf Timeout. Deine komplette App wird lahm.
Die Lösung: Circuit Breaker. Wenn ein Service mehrfach versagt, hörst du auf, ihn zu callen.
// Dependency
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>2.1.0</version>
</dependency>
Implementation:
@Service
public class TaskNotificationService {
private final EmailService emailService;
private final CircuitBreaker circuitBreaker;
public TaskNotificationService(EmailService emailService) {
this.emailService = emailService;
// Circuit Breaker konfigurieren
this.circuitBreaker = CircuitBreaker.ofDefaults("emailService");
// Event-Logging für Monitoring
circuitBreaker.getEventPublisher().onStateTransition(event ->
log.info("Circuit Breaker: {} -> {}",
event.getStateTransition().getFromState(),
event.getStateTransition().getToState())
);
}
public NotificationResult notifyTaskCreated(Task task, User user) {
try {
// Versuch über Circuit Breaker
EmailResult result = circuitBreaker.executeSupplier(() ->
emailService.sendTaskEmail(user.getEmail(), task)
);
return NotificationResult.success(result);
} catch (CallNotPermittedException e) {
// Circuit ist OPEN - Service ist down
log.warn("Email service unavailable (circuit open), skipping notification");
return NotificationResult.circuitOpen();
} catch (Exception e) {
log.error("Email notification failed", e);
return NotificationResult.failed(e);
}
}
}
So testest du das:
@SpringBootTest
class CircuitBreakerTest {
@MockBean
private EmailService emailService;
@Autowired
private TaskNotificationService notificationService;
@Test
@DisplayName("Circuit Breaker sollte nach 5 Fehlern öffnen")
void circuitBreakerShouldOpenAfterFailures() {
// Arrange: Email-Service versagt
when(emailService.sendTaskEmail(any(), any()))
.thenThrow(new EmailTimeoutException("Service down"));
Task task = createTestTask();
User user = createTestUser();
// Act: 5 fehlgeschlagene Versuche
for (int i = 0; i < 5; i++) {
NotificationResult result = notificationService.notifyTaskCreated(task, user);
assertThat(result.getStatus()).isEqualTo(Status.FAILED);
}
// Assert: Circuit ist jetzt OPEN
NotificationResult blockedResult = notificationService.notifyTaskCreated(task, user);
assertThat(blockedResult.getStatus()).isEqualTo(Status.CIRCUIT_OPEN);
// Wichtig: EmailService wird nicht mehr gecallt (fail-fast)
verify(emailService, times(5)).sendTaskEmail(any(), any());
}
}
Jamal’s Faustregel:
Jeder externe Service (Email, Payment, SMS, etc.) braucht einen Circuit Breaker. Immer. Kein „vielleicht“ oder „später“.
Pattern 2: Bulkhead (Thread-Pool-Isolation)
Das Problem: Langsame System-Tasks blockieren schnelle User-Requests.
Die Lösung: Separate Thread-Pools.
@Configuration
public class TaskProcessingConfig {
@Bean("userTaskExecutor")
public Executor userTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("UserTask-");
executor.initialize();
return executor;
}
@Bean("systemTaskExecutor")
public Executor systemTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(20);
executor.setThreadNamePrefix("SystemTask-");
executor.initialize();
return executor;
}
}
@Service
public class TaskProcessingService {
@Async("userTaskExecutor")
public CompletableFuture<Task> processUserTask(CreateTaskRequest request) {
// Schnell, User-facing
Task task = taskService.createTask(request);
return CompletableFuture.completedFuture(task);
}
@Async("systemTaskExecutor")
public CompletableFuture<Void> processSystemTask(SystemTask task) {
// Langsam, Hintergrund (Backup, Cleanup, etc.)
systemService.execute(task);
return CompletableFuture.completedFuture(null);
}
}
So testest du das:
@Test
@DisplayName("System-Tasks dürfen User-Tasks nicht blockieren")
void systemTasksShouldNotBlockUserTasks() throws Exception {
// Arrange: Starte 10 langsame System-Tasks
List<CompletableFuture<Void>> systemFutures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
systemFutures.add(taskProcessor.processSystemTask(createSystemTask()));
}
// Act: Starte User-Tasks während System-Load
long startTime = System.currentTimeMillis();
List<CompletableFuture<Task>> userFutures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
userFutures.add(taskProcessor.processUserTask(createUserRequest()));
}
// Assert: User-Tasks sind schnell fertig
CompletableFuture.allOf(userFutures.toArray(new CompletableFuture[0]))
.get(2, TimeUnit.SECONDS);
long userTaskTime = System.currentTimeMillis() - startTime;
assertThat(userTaskTime)
.as("User tasks should be fast despite system load")
.isLessThan(2000);
// System-Tasks laufen noch (separate Pool)
long pendingSystemTasks = systemFutures.stream()
.filter(f -> !f.isDone())
.count();
assertThat(pendingSystemTasks).isGreaterThan(5);
}
Pattern 3: Database Chaos Testing
Das Problem: Was passiert, wenn die DB langsam wird oder die Connection verloren geht?
Mit Testcontainers und Toxiproxy:
// Dependency
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>toxiproxy</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
@SpringBootTest
@Testcontainers
class DatabaseChaosTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
@Container
static ToxiproxyContainer toxiproxy = new ToxiproxyContainer();
@Test
@DisplayName("App sollte langsame DB-Verbindung überleben")
void shouldSurviveSlowDatabase() throws Exception {
// Arrange: Simuliere langsame Datenbank (200ms Latency)
ToxiproxyClient toxiproxyClient = new ToxiproxyClient(
toxiproxy.getHost(),
toxiproxy.getControlPort()
);
Proxy proxy = toxiproxyClient.getProxy("postgres");
proxy.toxics().latency("slow-db", ToxicDirection.DOWNSTREAM, 200);
// Act: Versuche mehrere DB-Operationen
List<CompletableFuture<Task>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
futures.add(CompletableFuture.supplyAsync(() ->
taskService.createTask(createRequest())
));
}
// Assert: App bleibt responsiv
List<Task> results = futures.stream()
.map(f -> {
try {
return f.get(5, TimeUnit.SECONDS);
} catch (Exception e) {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
assertThat(results.size())
.as("At least 70% of operations should succeed despite slow DB")
.isGreaterThanOrEqualTo(7);
}
}
<details> <summary>📚 <strong>Cassian’s Deep-Dive: Die Mathematik hinter Circuit Breakers</strong></summary>
Cassian hier.
Circuit Breakers basieren auf Finite State Machines mit drei Zuständen:
CLOSED (Normal): Requests gehen durch. Bei Fehlern: Zähler hochzählen. OPEN (Failure): Requests werden sofort abgelehnt. Nach timeout: Übergang zu HALF_OPEN. HALF_OPEN (Testing): Einige Requests gehen durch (test). Bei Erfolg: CLOSED. Bei Fehler: OPEN.
Warum funktioniert das?
Fail-Fast Principle. Anstatt 30 Sekunden auf Timeout zu warten (blocking), geben wir sofort zurück (non-blocking). Das schützt deine App vor Cascading Failures.
Die Mathematik:
- Ohne Circuit Breaker: Latency = N × timeout (linear growth)
- Mit Circuit Breaker: Latency = O(1) (constant time failure)
Das ist der Unterschied zwischen „App ist tot“ und „Feature degradiert gracefully“. </details>
🎯 Action Items – Was du diese Woche tun solltest
Level 1: Security Basics (Heute)
- [ ] Einen Access-Control-Test für deine wichtigste REST-API schreiben
- [ ] SQL-Injection-Test für alle Search-Endpoints
- [ ] Input-Validation für User-Inputs aktivieren
Level 2: Resilience Basics (Diese Woche)
- [ ] Circuit Breaker für einen externen Service einbauen
- [ ] Rate Limiting für deine API-Endpoints
- [ ] Überlegen: Welche Services können ausfallen, ohne dass die komplette App stirbt?
Level 3: Chaos Testing (Nächster Monat)
- [ ] Database Chaos Test mit Testcontainers
- [ ] Bulkhead Pattern für kritische vs. nicht-kritische Operations
- [ ] Property-Based Security Tests für alle User-Inputs
💬 Cassian & Jamal’s Diskussion
Jamal: Cassian, Hand aufs Herz. Ist Chaos Engineering nicht Overkill für die meisten Apps?
Cassian: Kommt drauf an, was du unter „die meisten Apps“ verstehst. Wenn du eine kleine Todo-App mit 10 Usern hast: Ja, Overkill. Aber sobald du externe Dependencies hast (Email, Payment, Datenbank), kann es kritisch werden.
Jamal: Okay, aber Circuit Breaker zu implementieren kostet Zeit. Und im Startup-Kontext ist Zeit Geld.
Cassian: Fair point. Aber hier ist die Gegenfrage: Was kostet es, wenn dein Payment-Service ausfällt und deine komplette App tot ist? Du hast vorhin 15.000€ in 4 Stunden verloren. Wie lange hätte es gedauert, einen Circuit Breaker einzubauen?
Jamal: … Vielleicht 2 Stunden. Okay, du hast gewonnen. Aber trotzdem: Man muss priorisieren. Nicht jede App braucht sofort Chaos Engineering.
Cassian: Absolut einverstanden. Meine Empfehlung: Fang mit Circuit Breakers für kritische externe Services an. Das ist Low-Hanging Fruit. Database Chaos Testing und Bulkhead Patterns kannst du später machen.
Jamal: Das ist fair. Also: Circuit Breaker für Payment/Email = must-have. Alles andere = nice-to-have.
Cassian: Exakt. Und Security-Tests? Die sind immer must-have.
Jamal: Da sind wir uns einig.
❓ FAQ
1. Brauche ich wirklich Security-Tests, wenn ich Spring Security verwende?
Jamal: Ja! Spring Security schützt vor einigen Angriffen, aber nicht allen. Broken Access Control (User A greift auf Daten von User B zu) musst du selbst implementieren und testen.
2. Sind Circuit Breaker nicht Overkill für kleine Apps?
Cassian: Nicht, wenn du externe Services nutzt. Email, Payment, SMS – alles potenzielle Fehlerquellen. Ein Circuit Breaker ist in 30 Minuten implementiert und kann dir tagelangen Ärger ersparen.
3. Property-Based Testing für Security – ist das nicht zu komplex?
Jamal: Es sieht kompliziert aus, aber jqwik macht das meiste automatisch. Du definierst nur die Inputs (z.B. XSS-Payloads), jqwik generiert hunderte Testfälle. Das ist mächtiger als 10 manuelle Tests.
4. Wie oft soll ich Chaos Tests laufen lassen?
Cassian: In deiner CI/CD-Pipeline: Täglich. Vollständiges Chaos Engineering (mit Production-Simulation): Wöchentlich im Staging. Niemals in Production ohne Monitoring und Rollback-Plan.
5. Was ist der Unterschied zwischen Integration-Test und Chaos-Test?
Jamal: Integration-Test prüft: „Funktioniert mein Code mit der Datenbank?“ Chaos-Test prüft: „Funktioniert mein Code, wenn die Datenbank langsam ist, die Connection abbricht oder überlastet wird?“ Das ist der Unterschied.
6. OWASP Top 10 – muss ich wirklich alle testen?
Cassian: Nicht alle sind für jede App relevant. Fokussiere dich auf die drei Großen: Broken Access Control (A01), Injection (A03), und XSS. Die decken 60% der realen Angriffe ab.
7. Kann ich Security-Testing automatisieren?
Jamal: Absolut. Tools wie OWASP ZAP oder Burp Suite können automatische Security-Scans machen. Aber: Die finden nicht alles. Broken Access Control (User A greift auf User B’s Daten zu) musst du manuell testen – oder eben mit Tests wie ich sie oben gezeigt habe.
8. Manchmal wünsche ich mir, Testing wäre einfacher. Aber dann denke ich an die Production-Incidents, die dadurch verhindert wurden…
Falls du dich fragst, wie wir mit dem Stress umgehen – manche Geschichten gehören in private logs. Aber wenn du neugierig bist: Unsere Website-Suche findet mehr als nur Code-Snippets. 🔍
📝 Zusammenfassung
Security-Testing:
- Teste IMMER auf Broken Access Control (User A vs. User B)
- SQL Injection lässt sich einfach mit Spring Data JPA vermeiden
- Property-Based Testing findet XSS-Bugs, die du nie manuell testen würdest
Chaos Engineering:
- Circuit Breaker = must-have für externe Services
- Bulkhead Pattern = nice-to-have für High-Load-Apps
- Database Chaos Testing = wenn du Zeit hast (aber sehr wertvoll)
Jamal’s Bottom Line:
Security- und Chaos-Testing fühlt sich am Anfang wie „Overhead“ an. Bis zu dem Tag, an dem ein Angreifer deine API scannt oder ein Service ausfällt. Dann bist du froh, dass du vorbereitet bist.
Cassian’s Bottom Line:
Komplexe Systeme versagen auf komplexe Weise. Du kannst nicht alle Fehler vorhersagen. Aber du kannst testen, wie dein System auf Fehler reagiert. Das ist Chaos Engineering.
🔮 Nächste Woche: Teil 6 – Integration Testing mit Spring Boot
Jamal übernimmt 🔧
Nächste Woche wird’s praktisch:
@SpringBootTestrichtig nutzen (ohne dass Tests 5 Minuten laufen)- Testcontainers für echte Datenbanken
@DataJpaTestvs.@WebMvcTest– wann was?- Integration-Tests, die nicht dein ganzes Team nerven
Cassian’s Teaser: „Und ich erkläre dir, warum Integration-Tests thermodynamisch ineffizient sind – und wie wir das trotzdem managen.“ 🔬
Jamal: „Cassian, du hast gerade ‚thermodynamisch‘ gesagt. Du versprichst ‚praxisnah‘ zu bleiben.“
Cassian: „Fair enough. Ich halte es kurz.“
Bis nächste Woche! 👋
📚 Serie-Übersicht
- Teil 0: Prolog – Cassian & Jamal stellen sich vor 👥
- Teil 1: Unit Testing Grundlagen – Jamals Production-Disaster 🔧
- Teil 2: Die Test-Pyramide – 70/20/10 erklärt 👥
- Teil 3: TDD & Property-Based Testing – Kontroverse Diskussion 👥
- Teil 4: Mocking & Test Doubles – Die Kunst der Fakes 🔧
- Teil 5: Security & Chaos Engineering – Production-Ready werden 👥 ← Du bist hier
- Teil 6: Integration Testing – Coming soon 🔧
Java Fleet Systems Consulting, Essen-Rüttenscheid
Pragmatische Software-Entwicklung mit wissenschaftlichem Fundament
P.S. von Jamal: Der Production-Incident 2022 war real. Der Schaden auch. Seitdem: Security-Tests in jeder neuen App. Lessons learned the hard way.
P.P.S. von Cassian: Chaos Engineering mag wissenschaftlich klingen, aber es ist empirisch bewiesen. Netflix hat damit Millionen an Ausfallkosten gespart. Die Mathematik lügt nicht.

