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
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.
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