Herstellen von initialen / sauberen Datenbankständen mit JUnit

In JUnit Tests werden oftmals Objekte angelegt und verändert, die dann über JPA in die Datenbank geschrieben werden. Für die Ausführung eines einzelnen Tests vielleicht noch kein Problem. Werden aber mehrere Tests (Test A und Test B) nacheinander ausgeführt, kommt es schnell dazu, dass Test A die Datenbank in einem Zustand hinterlässt, den Test B nicht erwartet und der Test somit fehlschlägt. Aus diesem Grund muss jeder Test vor seiner Ausführung seinen erwarteten Datenbank Stand herstellen.Was für einen Testdurchlauf im Continuous Integration Build vollkommen ausreichend ist (hier wird die Datenbank zu Job-Beginn neu erstellt und nach Job-Ende gelöscht), hat beim Ausführen in der lokalen Entwicklungsumgebung den unschönen Nebeneffekt, dass die Datenbank nach der Testdurchführung nur noch wenige Daten enthält. Dies ist insbesondere dann ärgerlich, wenn manuelles Tests, wie z.B. GUI-Tests durchgeführt werden sollen. Daher musste ein Weg gefunden werden, wie ein initialer Datenbank-Stand automatisiert nach der Durchführung der JUnit-Tests hergestellt werden kann.

Es galt also zwei Anforderungen umzusetzen:

  1. Löschen aller Datenbankeinträge vor jedem Test
  2. Wiederherstellung eines initialen Datenbankstands nach allen Tests

Lösung

Löschen aller Datenbankeinträge vor jedem Test

JUnit stellt zur Umsetzung dieser Anforderung die Möglichkeit zur Verfügung Methoden mit @Before zu markieren. Diese Methoden werden dann vor jeder Testausführung aufgerufen. Eine Möglichkeit wäre also, in jeder Testklasse eine solche Methode zu implementieren, bzw. in einer abstrakte Basisklasse diese Methode zu definieren und alle Testklassen von dieser abstrakten Basisklasse abzuleiten.

Eine weitere Variante ergibt sich, indem man einen eigenen JUnit4ClassRunner (http://www.cs.rice.edu/~javaplt/javadoc/concjunit4.7/org/junit/runners/BlockJUnit4ClassRunner.html) implementiert. Im JUnit4ClassRunnner kann in der runChild Methode vor jedem Test beliebiger Code implementiert werden.

Da die zweite Variante weniger invasiv ist, wurde diese bevorzugt.

Wiederherstellung eines intialen Datenbankstands nach allen Tests

Zur Umsetzung dieser Anforderung gibt es leider keine einfache Möglichkeit in JUnit. Es gibt zwar die Annotationen @BeforeClass und @AfterClass um vor bzw. nach allen Tests einer Testklasse etwas auszuführen, aber eben keine Möglichkeit vor, bzw nach allen Tests aller Testklassen etwas auszuführen. Empfohlen wird hierzu sich eine TestSuite einzurichten (siehe http://stackoverflow.com/questions/1967308/java-junit-test-suite-code-to-run-before-any-test-classes), wobei allerdings innerhalb dieser TestSuite alle Testklassen manuell aufgelistet werden müssen.

Eine weitere Möglichkeit besteht in der Verwendung von RunListener Implementierungen. Mit Hilfe eines RunListener ist es möglich, auf folgende Ereignisse während eines Testdurchlaufs zu reagieren (http://junit.sourceforge.net/javadoc/org/junit/runner/notification/RunListener.html):

Run Listener Methoden
testAssumptionFailure(Failure failure)
Called when an atomic test flags that it assumes a condition that is false
testFailure(Failure failure)
Called when an atomic test fails.
testFinished(Description description)
Called when an atomic test has finished, whether the test succeeds or fails.
testIgnored(Description description)
Called when a test will not be run, generally because a test method is annotated with Ignore.
testRunFinished(Result result)
Called when all tests have finished
testRunStarted(Description description)
Called before any tests have been run.
testStarted(Description description)
Called when an atomic test is about to be started.

Ein Nachteil der RunListener-Lösung ist, dass sie nicht beim Ausführen der Tests aus Eclipse heraus funktioni

ert. Daher unterscheidet sich die Lösung darin, ob die Testfälle mit Maven oder Eclipse(JUnit) ausgeführt werden:

  1. Maven
    Nachdem alle Testfälle durchlaufen worden sind, wird ein definierter Bestand an Testdaten wiederhergestellt. (siehe /business/src/test/resources/com/hec/business/generateTestData/testdaten )
  2. Eclipse(JUnit)
    Im Vergleich zu der Ausführung mit Maven wird der definierte Bestand an Testdaten direkt nach Abschluss eines Tests wiederhergestellt.

Umsetzung

Für die Umsetzung der zuvor beschriebenen Lösungen waren folgende Schritte notwendig:

  1. Erstellung einer JUnitClassRunner Klasse (erbt von SpringJUnit4ClassRunner).
    1. Überlagert die runChild Methode, um die Datenbank vor jedem Testfall zu säubern
    2. Überlagert die run Methode
      1. Ausführung aus Eclipse: Daten werden nach jedem Test wiederhergestellt
      2. Ausführung mit Maven: Daten werden nach allen Tests wiederhergestellt.
      3. Unterschieden wird dies durch die Property ismaven welche im maven-surefire-plugin definiert wird.(siehe unten)
  2. Erstellung einer InitializeDatabaseRunListener Klasse (erbt von RunListener)
    1. Ist dafür Zuständig den definierten Bestand an Testdaten wiederherzustellen.
      1. Wird bei der Ausführung aus Eclipse durch die JUnitClassRunner.run Methode nach jedem Test aufgerufen
      2. Wird bei der Ausführung mit Maven durch eine Konfiguration im maven-surefire-plugin am Ende aller Testfälle aufgerufen

 

Verfasst von Felix Huschle am 7. Februar 2017