Testcontainer Tests in Lichtgeschwindigkeit: Tests schneller und flexibler gestalten

Alexander Bierler

Testcontainers ist in vielen Java-Projekten zu einem unverzichtbaren Werkzeug geworden, um Integrations- und Systemtests in realistischen Umgebungen durchzuführen. Wenn Testszenarien jedoch komplexer werden, stoßen Entwickler oft an Grenzen – insbesondere in Bezug auf Konfiguration, Parallelisierung und Ressourcennutzung. Ein auf Testcontainers aufbauendes Open-Source-Framework begegnet einigen dieser Herausforderungen mit leistungsstarken Erweiterungen.

Testcontainers Infrastructure (TCI) Framework

1. Verbesserte Anpassbarkeit und Parallelisierung

Durch die Verwendung des Factory Patterns bei der Erstellung von Container wird es für den Entwickler einfacher, die Container nach Bedarf anzupassen:

Ohne TCIMit TCI
static final MySQLContainer MY_SQL_CONTAINER; static { MY_SQL_CONTAINER = new MySQLContainer(); MY_SQL_CONTAINER.start(); }static final DBTCIFactory DB_INFRA_FACTORY = new DBTCIFactory(); void startInfra() { this.dbInfra = DB_INFRA_FACTORY.getNew(...); }

Für jede “Infrastruktur” (Abgekürzt: TCI) gibt es eine Factory, um eine neue TCI zu erstellen. Diese Factory kann leicht konfiguriert werden und kümmert sich um Dinge wie die Erstellung von Containern, PreStarting und die Verfolgung der gestarteten TCI für die weiter unten genannten Features.

Container/Infrastruktur kann wie folgt in einer TCI_FACTORY-Klasse angepasst werden:

@Override
public void start(final String containerName) {
  super.start(containerName);
  if(doMigrate) {
    this.migrateDatabase(BASELINE_FOR_TESTS);
  }
}
void migrateDatabase(String version) {
  // Datenbank mithilfe von z.B. Flyway migrieren
}

Dies bedeutet, dass es nun möglich ist, zusätzlichen, nicht containerbezogenen Code hinzuzufügen, wie z.B. Clients oder allgemeine Methoden (z.B. createUser), ohne den Container selbst zu verändern. Dies folgt dem composition over inheritance Designprinizip.

Durch den Einsatz von Factories kann das Framework auch die Leistung mittels Parallelisierung und PreStarting verbessern.

2. Test so schnell wie möglich ausführen

Warum ist das überhaupt wichtig?

Die schnellstmögliche Ausführung von Tests hat mehrere Vorteile:

  • Bei Lokaler Ausführung:
    In der Regel gibt es nichts anderes zu tun, wenn man Tests lokal durchführt – außer vielleicht einen Kaffee zu trinken. Es ist auch möglich, eine andere Aufgabe zu beginnen, aber dann verliert man vielleicht den Fokus auf die ursprüngliche Aufgabe und muss sich später wieder in das Thema einarbeiten.
     
  • Bei Benutzung einer CI:
    • Wenn man für Rechenleistung bei Bedarf bezahlt (z. B. minutenbasierte Abrechnung von Spot-Instances) kann die schnellere Durchführung von Tests (ohne Vergrößerung des verwendeten Rechners) aufgrund der geringeren Mietdauer viel Geld sparen.
       
    • Wenn man für eine feste Menge an Rechenleistung bezahlt, bedeutet eine schnellere Ausführung von Tests, dass mehr Zeit für andere Aufgaben zur Verfügung steht, die auf der CI ausgeführt werden können. 
      Wenn die Zeitersparnis groß genug ist, kann man auch über eine Verringerung der erforderlichen Rechenleistung nachdenken.
       
    • Schnelleres Test-Feedback: Wenn z.B. vor einem Release alle Integrationstests erfolgreich ausgeführt werden müssen, kann dies die Zeit für die Auslieferung des Releases verkürzen.

Das Framework ist explizit auf Parallelisierung ausgelegt und bietet mehrere Funktionen zur Beschleunigung von Tests:

2.1. PreStarting Mechanismus

Bei der Durchführung von Tests gibt es in der Regel bestimmte Zeiten, in denen die verfügbaren Ressourcen kaum ausgelastet sind:

PreStarting verwendet einen gecachten Pool von Infrastrukturen und versucht, diese Leerlaufzeiten zu nutzen, um diesen Pool aufzufüllen. Wenn eine neue Infrastruktur angefordert wird, muss nicht auf ihre Erstellung gewartet werden, sondern es kann die bereits gestartete Infrastruktur aus diesem Pool verwendet werden – sofern sie verfügbar ist.

Performance-Boost

Wenn PreStarting korrekt implementiert wurde, kann dies einen enormen Leistungsunterschied bewirken, wie in diesem Leistungsvergleich zu sehen ist

Des Weiteren gibt es auch ein Live-Beispiel (mit GitHub Actions), das die folgenden Ergebnisse liefert:

FallParallelisierungPreStarting aktiv?Benötigte Zeit um alle Tests auszuführen
A8m 50s
B5m 30s
C26m
D24m 50s

Wie oben gezeigt, erzielt die beste Konfiguration (D) eine Geschwindigkeitssteigerung von fast 50 % im Vergleich zur Baseline (A).

2.2. Optimierte Testcontainers Netzwerke

Es wird eine optimierte Implementierung von Testcontainers Network verwendet:

VorherNachher
NetworkImpl code aus Testcontainers 1.20 – siehe unten.LazyNetworkPool bietet einen Pool von Netzwerken, die im Hintergrund erstellt werden.
Es geht keine Zeit mit dem Warten auf die Netzwerkerstellung verloren.
@Override
public synchronized String getId() {
  if (initialized.compareAndSet(false, true)) {
    boolean success = false;
    try {
      // Netzwerk wird erstellt wenn auf die ID zugegriffen wird
      // Dauert einen Moment
      id = create();
      success = true;
    } finally {
      ...
    }
  }
  return id;
}

2.3. Container Leck Erkennung

Erkennt ob gestartete Container/Infrastruktur auch beendet wurden und verhindert dadurch, dass es zur Ressourcenerschöpfung kommt.
Im folgenden Beispiel wird der Testcontainer zwar erstellt, aber nie beendet.

@Test
void test() {
  DummyTCI tci = DUMMY_FACTORY.getNew(...);
  ...
}

Nachdem die Tests mit dem Framework ausgeführt wurden, erscheint folgende Fehlermeldung:

ERROR s.x.tci.leakdetection.TCILeakAgent - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
ERROR s.x.tci.leakdetection.TCILeakAgent - ! PANIC: DETECTED CONTAINER INFRASTRUCTURE LEAK !
ERROR s.x.tci.leakdetection.TCILeakAgent - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
ERROR s.x.tci.leakdetection.TCILeakAgent - All test are finished but some infrastructure is still marked as in use:
DummyTCIFactory leaked 1x [container-ids=[c1b6be852fac3bf65ac8f2739ab161d7f95bc4c62699c698ccc8b74da1be8a3d]]

3. Quality of Life Verbesserungen

Das Framework enthält auch einige kleinere Verbesserungen:

3.1. Container-Namen die ein Mensch lesen kann

Alle gestarteten Container haben einen eindeutigen, von Menschen lesbaren Namen, was die Identifizierung erleichtert.

VorherNachher
docker stats

NAME
eager_rubin
vigilant_archimedes
practical_haibt
ecstatic_sanderson
serene_einstein
great_saha agitated_dhawan strange_montalcini
docker stats

NAME

selenium-chrome-2-PS-1-…
selenium-firefox-1-PS-1-…
selenium-chrome-1-PS-1-…
db-mariadb-1-PS-1-…
oidc-2-PS-1-…
selenium-firefox-2-PS-1-…
recorder-selenium-chrome-1-PS-1-…
recorder-selenium-firefox-1-PS-1-…..

3.2. Statistiken zur Testlaufzeit

Ein Nachverfolgungsmechanismus, der das Auffinden von Engpässen und ähnlichen Problemen erleichtert.

Beispiel:

[main] [i.tracing.TCITracingAgent] === Test Tracing Info ===
Duration: 2m 43.608s
Tests: 20.656s / 15 / 5m 9.84s
BrowserTCIFactory-firefox:
  bootNew - 1ms / 6 / 5ms
  connectToNetwork - 515ms / 5 / 2.575s
  getNew - 574ms / 5 / 2.87s
  infraStart(async) - 14.575s / 6 / 1m 27.448s
  postProcessNew - 54ms / 5 / 270ms
  warmUp - 2.448s / 1 / 2.448s
...

Weiterführende Infos

Das Framework ist auf GitHub verfügbar. Der Usage-Bereich enthält eine Einführung. Es stehen auch Demo-Projekte bereit.

Fragen oder Anregungen können über den GitHub Issue Tracker eingereicht werden.

Total
0
Shares
Previous Post

Wie man den Milliarden-Dollar-Fehler repariert

Next Post

MockServer NeoLight: Eine schlanke Alternative für API-Tests mit Testcontainers

Related Posts

PCI DSS-Sicherheitsauditverfahren – alles, was Sie wissen müssen

Die Einhaltung des Datensicherheitsstandards (DSS) der Zahlungskartenbranche (PCI, engl.: Payment Card Industry) erfordert eine jährliche Berichterstattung. Diese jährliche Berichterstattung umfasst umfangreiche PCI-DSS-Auditverfahren für Organisationen, die die höchsten Transaktionsvolumina abwickeln. Die Auditverfahren werden im Rahmen einer Vor-Ort-Bewertung durchgeführt, die als Konformitätsbericht (ROC, engl.: Report on Compliance) bezeichnet wird.
Read More