#Tests, Modernisierungsmuster und Refactoring-Regeln
Dieses Kapitel beschreibt Schutznetz, Charakterisierungstests, Ports-and-Adapters, Outbox und Entscheidungslogik pro Komponente.
#Tests und Schutznetz
#Reihenfolge
1. Smoke Test
2. Charakterisierungstest
3. Integrationstest
4. Contract Test
5. Regressionstest für Bugs
6. Test für extrahierte Use Cases
#Smoke Test
Ziel:
Kann die App starten?
Kann Login funktionieren?
Kann Hauptseite geladen werden?
Kann ein einfacher Use Case laufen?
#Charakterisierungstest
Ein Charakterisierungstest dokumentiert bestehendes Verhalten, auch wenn es unschön ist.
@Test
void auditIsCommittedEvenWhenOrderCreationFails() {
try {
orderService.createOrderThatFailsAfterAudit();
} catch (Exception ignored) {
}
assertTrue(auditRepository.contains("ORDER_CREATED"));
assertFalse(orderRepository.exists(orderId));
}
#SOAP Golden Master
src/test/resources/golden/customer-update-request.xml
src/test/resources/golden/customer-update-response.xml
Teste XML semantisch:
- Whitespace ignorieren
- Namespaces beachten
- dynamische Felder ignorieren
- Zeitstempel und IDs normalisieren
#JMS Handler Test
Statt MDB-Container zu testen, Handler extrahieren:
@Test
void duplicateOrderCreatedMessage_doesNotCreateSecondInvoice() {
handler.handle(message);
handler.handle(message);
assertEquals(1, invoiceRepository.countByOrderId(orderId));
}
#Modernisierungsmuster
#Ports and Adapters
Port:
public interface OrderEventPublisher {
void publishOrderCreated(OrderId orderId);
}
Legacy Adapter:
public class JmsOrderEventPublisher implements OrderEventPublisher {
private final QueueSender sender;
public JmsOrderEventPublisher(QueueSender sender) {
this.sender = sender;
}
@Override
public void publishOrderCreated(OrderId orderId) {
sender.send("ORDER_CREATED", orderId.value());
}
}
#Use Case extrahieren
public class CreateOrderUseCase {
private final OrderRepository orderRepository;
private final AuditPort auditPort;
private final OrderEventPublisher eventPublisher;
public CreateOrderUseCase(
OrderRepository orderRepository,
AuditPort auditPort,
OrderEventPublisher eventPublisher
) {
this.orderRepository = orderRepository;
this.auditPort = auditPort;
this.eventPublisher = eventPublisher;
}
public OrderId execute(CreateOrderCommand command) {
Order order = Order.create(command.customerId(), command.items());
orderRepository.save(order);
auditPort.orderCreated(order.id());
eventPublisher.publishOrderCreated(order.id());
return order.id();
}
}
#Anti-Corruption Layer
Externe SOAP/JAXB-Klassen nicht in die Domain lassen.
public ApprovalCommand toCommand(CustomerApprovalSoapType soapType) {
return new ApprovalCommand(
new CustomerId(soapType.getCustomerNo()),
"Y".equals(soapType.getApprovedFlag())
);
}
#Outbox Pattern
Problem:
@Transactional
public void createOrder() {
orderRepository.save(order);
jmsPublisher.publish(new OrderCreatedEvent(order.id()));
}
Besser:
In derselben DB-Transaktion:
- Order speichern
- Outbox Event speichern
Nach Commit:
- Publisher liest Outbox
- sendet JMS/Kafka/etc.
- markiert Event als published
Tabelle:
CREATE TABLE outbox_event (
id VARCHAR(36) PRIMARY KEY,
aggregate_id VARCHAR(36),
event_type VARCHAR(100),
payload CLOB,
status VARCHAR(20),
created_at TIMESTAMP,
published_at TIMESTAMP
);
#Entscheidungslogik pro Komponente
Nutze diese Strategien:
KEEP → vorerst lassen
WRAP → hinter Interface kapseln
EXTRACT → Fachlogik in Plain Java ziehen
REPLACE → Technologie ersetzen
DELETE → ungenutzten Code entfernen
SPLIT → fachlich trennen
Vorlage:
# Modernization Candidate: <Name>
## Fachlicher Zweck
## Aktuelle technische Form
- EJB / JSP / SOAP / JMS / DAO / Batch:
- Klasse:
- Modul:
- Deployment-Artefakt:
## Entry Points
- Wird aufgerufen von:
- Ruft selbst auf:
## Transaktion
- TransactionAttribute:
- Nested TX:
- Rollback-Regeln:
- XA beteiligt:
## Side Effects
- DB Writes:
- JMS Sends:
- SOAP Calls:
- Files:
- Emails:
## Risiken
| Risiko | Beschreibung | Schwere |
|---|---|---|
| Transaktion | ... | hoch |
| Messaging | ... | mittel |
## Tests vorhanden
- Unit Tests:
- Integration Tests:
- Manuelle Tests:
- Logs/Monitoring:
## Empfohlene Strategie
- KEEP / WRAP / EXTRACT / REPLACE / DELETE / SPLIT
## Erster sicherer Schritt
## Nicht anfassen ohne Test
- Transaktion
- Exception Handling
- SOAP Payload
- Queue Semantik
- Security
#Checkliste vor jedem Refactoring
Vor Änderung:
- Habe ich den Entry Point verstanden?
- Habe ich die Call Chain dokumentiert?
- Kenne ich die Transaktionsgrenze?
- Kenne ich alle DB Writes?
- Kenne ich alle JMS Sends?
- Kenne ich alle SOAP Calls?
- Kenne ich Exception/Rollback-Verhalten?
- Gibt es REQUIRES_NEW?
- Gibt es NOT_SUPPORTED?
- Gibt es Security Context?
- Gibt es JNDI Lookups?
- Gibt es mindestens Smoke/Charakterisierungstest?
- Habe ich einen Rollback-Plan?
Nach Änderung:
- Build erfolgreich?
- Deployment erfolgreich?
- Smoke Test erfolgreich?
- Haupt-Use-Case erfolgreich?
- Logs unauffällig?
- Transaktion unverändert?
- SOAP Payload unverändert?
- JMS Destination unverändert?
- DB Schema unverändert?
- Exception-Verhalten unverändert?