Find all JUnit tests in a project

In einem Kundenprojekt haben wir verschiedene Kategorien von Tests eingeführt: Unit-Tests, Integration-Tests und Selenium-Tests. Da die Tests unterschiedliche Konfigurationen erfordern (z. B. Selenium Server starten) und auch die Testausführung unterschiedlich lang ist, wollten wir diese Tests in Testsuiten von JUnit aufnehmen.

Testsuiten haben einige Vorteile, so lässt sich die Testreihenfolge festlegen (auch wenn Tests prinzipiell unabhängig voneinander sein sollten, kann es bspw. dazu genutzt werden Testdaten in das System einzufahren). Ein weiterer Vorteil besteht darin, dass Testsuiten von allen IDEs und Buildsystemem gestartet werden können. IntelliJ IDEA erlaubt keine regulären Ausdrücke bei der Auswahl der Tests, was dazu führt, dass man die *IntegrationTest.java nicht getrennt von den *Test.java Klassen ausführen kann. Stattdessen müssen immer Einstiegsklassen in die Tests angegeben werden.
Maven, genauer das Surefire-Plugin, dagegen unterstützt nur eine Filterung auf Dateiebene über die Include-Filter.
Testsuiten sind gewönliche Java-Klassen die mit @Suite annotiert werden. Diese Java-Klasse kann nun von allen IDEs und Build-Systemen als Startpunkt für die Tests genutzt werden.

@RunWith(Suite.class)
@Suite.SuiteClasses({
  UserUnitTest.class,
  AnotherUnitTest.class
})
public class UnitTestSuite {}

Der riesengroße Nachteil von Testsuiten ist, dass sie gepflegt werden müssen. Werden neue Tests geschrieben, müssen die Klassen der Suite hinzugefügt werden, da sie sonst vom CI-Server nicht ausgeführt werden. Das führte bei uns im Projekt nach relativ kurzer Zeit dazu, dass einige Testklasse zwar angelegt wurden, aber nicht in der Suite enthalten waren. Zum Glück haben wir die Shell und ein Skript zum Sammeln aller Unit-Tests ist schnell geschrieben:

find src/test/java/ -name '*.java' |
xargs grep -l "@Test" |
xargs grep -L "extends TestSetup" |
xargs grep -L "@RunWith" |
xargs basename |
sed 's/.java/.class,/g' |
sort

Der Phantasie sind natürlich keine Grenzen gesetzt, wie jetzt genau die Tests als Unit-, Integration- oder Selenium-Test erkannt werden. Im Beispiel werden alle Java-Klassen gefunden, die mindestens eine @Test Annotation haben, aber nicht von TestSetup erben und bei denen es sich nicht um Spring-Testcontext-Tests handelt, da diese im Projekt mit @RunWith markiert sind.

Wichtig ist lediglich die geeigneten grep Optionen zu verknüpfen:

  • grep -l: Das kleine L listet alle Treffer
  • grep -L: Findet alle Dateien, die nicht treffen
  • basename: Liefert den Dateinamen, damit er direkt in die Suite-Klasse eingesetzt werden kann
  • sed: Pure Faulheit: Macht aus den .java Endungen .class, damit die Ergebnisse per Copy & Paste eingesetzt werden können

Leider ist immer noch ein manuelles Prüfen der Tests notwendig, selbst wenn uns die Shell-Skripte jetzt alle Klassen liefern. Man könnte also einfach vor jedem Build dieses Skript ausführen, um alle Test-Klassen zu finden.

Gibt es bessere Vorschläge, TestSuiten automatisch zu befüllen? Ich freue mich auf Kommentare.