Wie erstelle ich skalierbare und nebenwirkungsfreie Integrationstests?

14

In meinem aktuellen Projekt fällt es mir schwer, eine gute Lösung zu finden, um skalierbare Integrationstests zu erstellen, die keine Nebenwirkungen haben. Ein wenig Klarheit über die nebenwirkungsfreie Eigenschaft: Es geht hauptsächlich um die Datenbank; Nach Abschluss der Tests sollten keine Änderungen in der Datenbank vorgenommen werden (der Status sollte beibehalten werden). Vielleicht passen Skalierbarkeit und Zustandserhaltung nicht zusammen, aber ich möchte unbedingt eine bessere Lösung anstreben.

Hier ist ein typischer Integrationstest (diese Tests betreffen die Datenbankebene):

public class OrderTests {

    List<Order> ordersToDelete = new ArrayList<Order>(); 

    public testOrderCreation() {
        Order order = new Order();
        assertTrue(order.save());
        orderToDelete.add(order);
    }

    public testOrderComparison() {
        Order order = new Order();
        Order order2 = new Order();
        assertFalse(order.isEqual(order2);
        orderToDelete.add(order);
        orderToDelete.add(order2);
    }
    // More tests

    public teardown() {
         for(Order order : ordersToDelete)
             order.delete();
    }
}

Wie man sich vorstellen kann, führt dieser Ansatz zu extrem langsamen Tests. Bei den gesamten Integrationstests dauert es ungefähr 5 Sekunden, um nur einen kleinen Teil des Systems zu testen. Ich kann mir vorstellen, dass diese Zahl steigt, wenn die Abdeckung erhöht wird.

Was wäre ein anderer Ansatz, um solche Tests zu schreiben? Eine Alternative, die ich mir vorstellen kann, besteht darin, globale Variablen (innerhalb einer Klasse) zu haben, und alle Testmethoden verwenden diese Variable gemeinsam. Infolgedessen werden nur wenige Aufträge erstellt und gelöscht. was zu schnelleren Tests führt. Ich denke jedoch, dass dies ein größeres Problem mit sich bringt; Die Tests sind nicht mehr isoliert und es wird immer schwieriger, sie zu verstehen und zu analysieren.

Es kann sein, dass Integrationstests nicht so oft ausgeführt werden sollen wie Unit-Tests. daher könnte eine geringe Leistung für diese akzeptabel sein. In jedem Fall wäre es gut zu wissen, ob jemand Alternativen zur Verbesserung der Skalierbarkeit gefunden hat.

Guven
quelle

Antworten:

6

Informieren Sie sich über die Verwendung von Hypersonic oder einer anderen speicherinternen Datenbank für Komponententests. Die Tests werden nicht nur schneller ausgeführt, sondern Nebenwirkungen sind auch nicht relevant. (Durch Zurücksetzen der Transaktionen nach jedem Test können auch viele Tests auf derselben Instanz ausgeführt werden.)

Dies zwingt Sie auch dazu, Daten-Mockups zu erstellen, was eine gute Sache ist, IMO, da dies bedeutet, dass etwas, das in der Produktionsdatenbank vorkommt, nicht unerklärlicherweise anfängt, Ihre Tests nicht zu bestehen, UND es gibt Ihnen einen Ausgangspunkt für eine "saubere Datenbankinstallation" "würde aussehen, was hilft, wenn Sie plötzlich eine neue Instanz der Anwendung ohne Verbindung zu Ihrer vorhandenen Produktionsstätte bereitstellen müssen.

Ja, ich verwende diese Methode selbst, und ja, es war ein PITA, um das ERSTE Mal einzurichten, aber es hat sich in der Laufzeit des Projekts, das ich abschließen werde, mehr als bezahlt gemacht. Es gibt auch eine Reihe von Tools, die bei der Erstellung von Datenmodellen helfen.

SplinterReality
quelle
Klingt nach einer großartigen Idee. Mir gefällt vor allem der Aspekt der vollständigen Isolation. Beginnen Sie mit einer neuen Datenbankinstallation. Es scheint, dass es einige Anstrengungen erfordern wird, aber wenn es einmal eingerichtet ist, wird es sehr vorteilhaft sein. Vielen Dank.
Guven
3

Dies ist das ewige Problem, mit dem jeder beim Schreiben von Integrationstests konfrontiert ist.

Die ideale Lösung, insbesondere wenn Sie Tests in der Produktion durchführen, besteht darin, eine Transaktion im Setup zu öffnen und im Teardown zurückzusetzen. Ich denke, das sollte Ihren Bedürfnissen entsprechen.

Wenn dies nicht möglich ist, beispielsweise wenn Sie die App von der Client-Ebene aus testen, besteht eine andere Lösung darin, eine Datenbank auf einer virtuellen Maschine zu verwenden, einen Snapshot im Setup zu erstellen und in teardown darauf zurückzukehren (dies ist nicht der Fall) Dauern Sie so lange, wie Sie vielleicht erwarten.

pdr
quelle
Ich kann nicht glauben, dass ich nicht an das Rollback der Transaktion gedacht habe. Ich werde diese Idee als schnelle Lösung verwenden, aber letztendlich klingt die speicherinterne Datenbank sehr vielversprechend.
Guven
3

Integrationstests sollten immer mit dem Produktionsaufbau abgeglichen werden. In Ihrem Fall bedeutet dies, dass Sie über denselben Datenbank- und Anwendungsserver verfügen sollten. Natürlich können Sie sich aus Performancegründen für einen In-Memory-DB entscheiden.

Sie sollten jedoch niemals Ihren Transaktionsumfang erweitern. Einige Leute schlugen vor, die Kontrolle über die Transaktion zu übernehmen und sie nach den Tests zurückzusetzen. Wenn Sie dies tun, alle Entitäten (vorausgesetzt, Sie verwenden die JPA) bleiben während der gesamten Testausführung an den Persistenzkontext gebunden . Dies kann zu einigen sehr bösen Fehlern führen, die sehr schwer zu finden sind .

Stattdessen sollten Sie die Datenbank nach jedem Test manuell über eine Bibliothek wie JPAUnit löschen oder ähnlich diesem Ansatz (löschen Sie alle Tabellen JDBC verwenden).

Aufgrund Ihrer Leistungsprobleme sollten Sie die Integrationstests nicht bei jedem Build ausführen. Lassen Sie dies Ihren Continuous Integration Server tun. Wenn Sie Maven verwenden, könnte Ihnen das gefallen ausfallsichere Plug-In gefallen , mit dem Sie Ihre Tests in Unit- und Integrationstests unterteilen können.

Sie sollten auch nichts verspotten. Denken Sie daran, dass Sie Integrationstests durchführen, dh das Verhalten in der Laufzeit-Ausführungsumgebung testen.

BenR
quelle
Gute Antwort. Ein Punkt: Es kann akzeptabel sein, einige externe Abhängigkeiten zu verspotten (z. B. das Senden von E-Mails). Aber Sie sollten sich lieber über einen kompletten Mock-Service / Server lustig machen, anstatt ihn im Java-Code zu verspotten.
sleske
Sleske, ich stimme dir zu. Insbesondere, wenn Sie JPA, EJB, JMS verwenden oder gegen eine andere Spezifikation implementieren. Sie können den Anwendungsserver, den Persistenzanbieter oder die Datenbank austauschen. Zum Beispiel möchten Sie vielleicht Embedded Glassfish und HSQLDB verwenden, um einfach die Geschwindigkeit einzurichten und zu verbessern (Sie können natürlich auch eine andere zertifizierte Implementierung wählen).
BenR
2

Auf Skalierbarkeit

Ich hatte vor einiger Zeit das Problem, dass die Integrationstests zu lange dauerten und für einen einzelnen Entwickler nicht praktikabel waren, um in einem engen Regelkreis von Änderungen fortlaufend ausgeführt zu werden. Einige Strategien, um damit umzugehen, sind:

  • Schreiben Sie weniger Integrationstests - Wenn Sie eine gute Abdeckung mit Komponententests im System haben, sollten sich die Integrationstests mehr auf (Ahem-) Integrationsprobleme konzentrieren und darauf, wie verschiedene Komponenten zusammenarbeiten. Sie ähneln eher Rauchtests, bei denen nur versucht wird, festzustellen, ob das System als Ganzes noch funktioniert. Im Idealfall decken Ihre Komponententests die meisten funktionalen Teile der Anwendung ab, und die Integrationstests überprüfen lediglich, wie diese Teile miteinander interagieren. Sie benötigen hier keine umfassende Abdeckung der logischen Pfade, sondern nur einige kritische Pfade durch das System.
  • Führen Sie jeweils nur einen Teil der Tests aus. Als einzelner Entwickler, der an bestimmten Funktionen arbeitet, haben Sie normalerweise ein Gespür dafür, welche Tests erforderlich sind, um diese Funktionen abzudecken. Führen Sie nur die Tests durch, die sinnvoll sind, um Ihre Änderungen abzudecken. Mit Frameworks wie JUnit können Sie Fixtures in einer Kategorie gruppieren . Erstellen Sie die Gruppierungen, die für jede Funktion die beste Abdeckung ermöglichen. Natürlich möchten Sie immer noch alle Integrationstests ausführen, bevor Sie sich für das Versionsverwaltungssystem und auf jeden Fall für den Continuous Integration Server entscheiden.
  • Optimieren Sie das System - Integrationstests in Verbindung mit einigen Leistungstests geben möglicherweise den erforderlichen Input, um langsame Teile des Systems zu , damit die Tests später schneller ausgeführt werden. Dies kann dazu beitragen, erforderliche Indizes in der Datenbank, gesprächige Schnittstellen zwischen Subsystemen oder andere Leistungsengpässe, die behoben werden müssen, aufzudecken.
  • Führen Sie Ihre Tests parallel aus - Wenn Sie eine gute Gruppierung orthogonaler Tests haben, können Sie versuchen, sie parallel auszuführen . Achten Sie jedoch auf die orthogonale Anforderung, die ich erwähnt habe. Sie möchten nicht, dass Ihre Tests sich gegenseitig in die Füße treten.

Versuchen Sie, diese Techniken zu kombinieren, um eine größere Wirkung zu erzielen.

Jordão
quelle
1
Wirklich gute Richtlinien; Das Schreiben weniger Integrationstests ist mit Sicherheit der beste Rat. Außerdem wäre es eine sehr gute Alternative, sie parallel zu betreiben. Ein perfekter "Test", um zu überprüfen, ob ich meine Tests in Bezug auf die Isolation richtig gemacht habe.
Guven
2

Zu Testzwecken haben wir eine dateibasierte Bereitstellung einer SQLite-Datenbank verwendet (kopieren Sie einfach eine Ressource). Dies wurde durchgeführt, damit wir auch Schemamigrationen testen können. Soweit mir bekannt ist, sind Schemaänderungen nicht transaktional und werden daher nicht zurückgesetzt, nachdem eine Transaktion abgebrochen wurde. Wenn Sie für die Testeinrichtung nicht auf die Transaktionsunterstützung angewiesen sind, können Sie das Verhalten von Transaktionen in Ihrer Anwendung ordnungsgemäß testen.

Carlo Kuip
quelle