Zum Hauptinhalt springen

EJUnit

EJUnit ist ein JUnit 4 (4.12) Runner für die Ausführung von JUnit-Tests in Java zu Programmieraufgaben. Der Runner ist eine Erweiterung von älteren Projekten (JUnitXmlFormatter, Schmant, Ant) und wurde speziell für die Einbindung in Systeme zur Auswertung von Lösungen zu Programmieraufgaben entwickelt.

Neue Features:

  • Testausführung und Erstellung von Ergebnis-Report im TREX-Format
    • Unterscheidung zwischen Failures (Asserts) und Errors (Runtime Exceptions)
    • Handling von Timeouts durch Endlosschleifen: Generierung von Testergebnissen trotz Endlosschleife
  • Isolierte Ausführung von JUnit-Testfällen in separaten JVMs, welche bei Endlosschleifen in einem Testfall die Ausführung anderer Testfälle ermöglicht (vgl. dieses Maven Plugin)
  • Einbindung von Bytecode Instrumentierung mit JaCoco pro Testfall in JUnit
  • Erstellung von Coverage Reports (pro Testfall) im CEXF-Format für JaCoCo und OpenClover

Build

Mit ./gradlew build den Runner bauen. Anschließend befinden sich alle jars unter build/libs.

Testausführung

Die Testausführung kann in zwei verschiedenen Modi erfolgen, die nachfolgend erläutert werden. In beiden Modi ist es möglich, den Quellcode zu instrumentieren. Bei JaCoCo werden *.exec Dateien pro Testfall bei der Testausführung im Arbeitsverzeichnis erzeugt. Für JaCoCo ist die Konfiguration des Agenten notwendig, der als jacocoagent.jar nach dem Build unter build/libs zur Verfügung steht. Bei OpenClover muss vorher der Quellcode instrumentiert werden. Hierfür kann die ejunit-openclover.jar verwendet werden.

Als Beispiele zur Verwendung dienen die Skripte in den Evaluators (z.B. unter evaluators/shared/java).

Modus: Mono

Für die Ausführung von allen Testfällen in einer JVM.

usage: ejunit-mono [OPTIONS] [selector1 [selector2 [selector3] ...]]
Executes all JUnit test methods in the test classes identified by the
given selectors in the SAME JVM AS THE TEST RUNNER. A selector is either a
FQCN of a test class (all test methods) or an identifier in the format
<testclass >#<testmethod> (single test method). Test classes need to be on
the class path with the code under test.

OPTIONS:
-jacoco,--with-jacoco Enable per-test coverage instrumentation
with JaCoCo (online). The jacocoagent.jar
needs to be configured using the -javaagent
JVM flag. Per-test coverage data will be
saved in the working directory after each
test in the following format:
<testclass>#<testmethod>.exec
-o,--out <arg> Output path for the resulting TREX report.
-s,--max-output-size <arg> Maximum number of characters to include in
stdout/stderr outputs from testcases before
truncation. If the output is larger, only
the last ARG characters are kept and a
truncation notice is added to the output.
Defaults to 4096.

Modus: Isolated Execution

info

Das war ein Prototyp, der inzwischen so nicht mehr im Einsatz ist. Es gibt diverse Probleme, wenn der Prozess für die Koordination der Testausführung im selben Container läuft wie die Testausführung selbst. (Starvation, zum Großteil redundantes Docker Image, einheitliche CPU- und Speichernutzung im Vergleich zu Mono, ...). Das selbe Verhalten übernimmt jetzt der javaie Evaluator, der außerhalb des Docker Containers läuft.

Für die isolierte Ausführung von Testfällen jeweils in einer JVM pro Testfall.

usage: ejunit-ie-master [OPTIONS] [class1 [class2 [class3] ...]]
Executes all JUnit test methods in the given test classes identified by
their FQCNs in SEPARATE JVMs. Test classes need to be on the class path
with the code under test.

OPTIONS:
-agent,--jacoco-agent <arg> Path to the jacocoagent jar file for
enabling per-test coverage instrumentation
with JaCoCo (online). Per-test coverage
data will be saved in the working directory
after each test in the following format:
<testclass>#<testmethod>.exec
-o,--out <arg> Output path for the resulting TREX report.
-t,--timeout <arg> Execution timeout (per testcase) in
seconds.
-w,--worker <arg> Path to the IE worker jar file.

If all test methods in the given test classes have finished, exit with
status 0. This is independent from the result of the test case, meaning
that even if test failures or errors occur, the status is 0. If an
unexpected exception occurs in the test runner itself, return status 1.

In contrast to the mono runner, test methods are executed in their own
JVM. Test methods are executed sequentially (not in parellel). For each
test method, a JVM processes is started and waited for a result. If the
JVM does not terminate after the configured timeout, the JVM process is
killed. This will ensure that test methods running indefinitely will not
prevent other test methods from being executed. Thus, this process may run
for a total time = timeout * number of test methods in the test classes.

There is a significant overhead due to the start of a separate JVM for
each test method, which slows down test execution in comparison to the
mono runner.

JVM processes are started with the -XX:MaxRAMPercentage=50 flag (to enable
comparison with the mono runner) and inherit some flags from the spawning
JVM (-ea, --enable-preview).

Testmethoden aus Testklassen extrahieren

usage: ejunit-ls [OPTIONS] [class1 [class2 [class3] ...]]
Extracts all test methods from the given test classes as testcase
identifiers, that can be used as input for the mono runner. Test classes
need to be on the class path with the code under test.

OPTIONS:
-o,--out <arg> Output path for writing the testcase identifiers.
Testcase identifiers are written in the format
<testclass>#<testmethod> into a text file with one
identifier per line.

Coverage Reports erzeugen

Nachdem die Tests auf dem instrumentierten Code mit ejunit-mono bzw. ejunit-ie-master ausgeführt wurden, kann der CEXF Coverage-Report erzeugt werden.

JaCoCo

Unter cexf/jacoco befindet sich der Report Generator, der den Report aus den *.exec Dateien erzeugt.

usage: ejunit-jacoco [OPTIONS] directory
Parses all *.exec files generated by JaCoCo in the given directory and
generates a CEXF report.
OPTIONS:
-ff,--file <arg> FQCN of a class file for filtering code coverage. The
class needs to be on the class path. If no class with
the specified FQCN is found on the class path, the
report will be empty.
-lt,--ltype <arg> Code coverage granularity to include in the report.
One of the following: instr, cond, line. If not
specified, all types will be included in the report.
-o,--out <arg> Output path for the resulting CEXF report.

OpenClover

Bei der Testausführung wird eine interne Datenbank von OpenClover angelegt. cexf/openclover erzeugt daraus den Report.

  USAGE: ejunit-openclover [OPTIONS] PARAMS

PARAMS:
-i, --initstring <string> clover initstring

-ff, --file <string> FQCN of a class file for filtering code coverage.
In contrast to the JaCoCo report generator, the class is NOT
required to be on the class path.

OPTIONS:
-o, --outfile <dir> the file to write report to.

-lt, --ltype <string> Code coverage granularity to include in the report.
One of the following: instr, cond, line, stmt, method, class.
If not specified, all types will be included in the report.

Debuggen mit Gradle

Die :mono:runSample und :ie:master:runSample Tasks starten die Ausführung der übergegebenen Testklassen im jeweiligen Modus. Die Instrumentierung mit JaCoCo wird dabei ebenfalls aktiviert. Die Testklassen (und die Klassen under Test) müssen sich im samples-Subprojekt befinden und werden durch die Ausführung des Tasks mit Gradle automatisch dem Classpath hinzugefügt. Anschließend kann mit dem :cexf:jacoco:generate Task ein CEXF-Report erstellt werden.

Beispiel:

./gradlew :mono:runSample -PappArgs='sample.PingTest'
./gradlew :cexf:jacoco:generate # parsed alle *.exec Dateien im aktuellen Verzeichnis

Die Testausführung mit OpenClover kann (noch) nicht gedebuggt werden. Man kann es aber über das test.sh Bash-Skript testen:

cli/openclover/test.sh -ff sample.Ping sample.PingTest

Unter cli/openclover/build/report finden sich dann Reports in diversen Formaten, inklusive dem CEXF-Report. Die Generierung des CEXF-Reports kann aber mit Gradle ausgeführt und daher auch gedebuggt werden:

./gradlew cexf:openclover:report -PappArgs='--file sample/Ping.java -i cli/openclover/build/cli/db/clover.db'

Es kann auch eine IntelliJ Run-Konfiguration (Gradle) mit dem Task erstellt werden, um Debuggen zu können. Unbedingt darauf achten, dass ejunit als Gradle Projekt ausgewählt wird, damit das Arbeitsverzeichnis auf ejunit gesetzt wird.

note

Ohne Gradle klappt es in IntelliJ nicht, da der Classpath aufgrund eines Bugs nicht erweitert werden kann.


Weitere Anmerkungen zur Architektur

Struktur

Der Quellcode aller Komponenten ist in einer Multi-Project Struktur mit Gradle organisiert, um Redundanzen zu vermeiden.

  • Im samples-Projekt befinden sich ein paar Quellcode-Dateien zum Testen, welche unterschiedliche JUnit Features beinhalten und auch Fälle wie Endlosschleifen, Endlosrekursion usw. abdecken. Dieses Projekt wird in vielen Projekten eingebunden und ermöglicht das bequeme manuelle Testen über z.B. runSample. Außerdem gibt es ein paar automatische Tests, die diesen Quellcode verwenden.
  • Das trex-Projekt enhält sämtliche Klassen für die Generierung des TREX Reports anhand von JUnit.
  • Im common-Projekt befinden sich Hilfsklassen für die Verwendung in allen Projekten