Zum Haupinhalt springen Zum Fußbereich springen
Test-Fixtures: Abhängigkeiten in Gradle verwalten

Test-Fixtures: Abhängigkeiten in Gradle verwalten

Autor:in: Paul-Fiete Hartmann

Gradle ermöglicht die Aufteilung einer Applikation in Subprojekte. Bei der Definition der Abhängigkeiten wollen die Test-Fixtures nicht in das Schema von Produktions- und Testcode passen. In diesem Artikel stellen wir ein weniger beachtetes Gradle Feature vor, mit dem auch Test-Fixtures ihren natürlichen Platz in dem System aus Abhängigkeiten finden.


Test-Fixtures sind Hilfsklassen und Ressourcen, auf denen Tests aufbauen. Beispielsweise

  • die Initialisierung einer Model-Klasse mit Testdaten
  • Hilfsmethoden zur json-Manipulation und Auswertung
  • vielfach genutzte Mocking-Konstruktionen

Häufig entstehen Test-Fixtures durch Extraktion von Code, der in mehreren Test-Klassen zum Einsatz kommt. Interessant wird es, wenn dies über mehrere (Sub-)Projekte hinweg geschehen soll.

Das Problem: Weder Test- noch Produktionscode

Etwas konkreter soll hier eine Applikation mit drei Subprojekten als Beispiel dienen: app, domain und exporters:

Unmögliche Abhängigkeiten zwischen Test-Projekten
Die Tests im `domain`-Projekt benötigen konkrete Testdaten. Diese werden in einer separaten Klasse `TestDataFactory` aufgebaut. Um den json-Exporter im `exporters`-Projekt zu testen, würde diese Factory auch eine nützliche Grundlage bilden. Aber eine Abhängigkeit kann nicht direkt definiert werden. [^workaround] Diese Einschränkung ist eine bewusste Design-Entscheidung in Gradle, da Tests im Normalfall nur ausgeführt werden, aber selbst keine Artefakte für andere Projekte bereitstellen.

Die Lösung: Das java-test-fixtures Plugin

Mit Gradle 5.6 werden Test-Fixtures nativ unterstützt:

Lösung über Test-Fixtures

Dazu wird in dem Projekt, welches Test-Fixtures bereitstellt, das entsprechende Gradle-Plugin eingebunden:

domain/build.gradle:

plugins {
    id 'java-library'
    id 'java-test-fixtures'
}

dependencies {
    // ...
    testFixturesImplementation 'com.google.guava:guava:31.1-jre'
}

Gradle erzeugt und konfiguriert dann automatisch ein neues SourceSet testFixtures, neben main und test. Die Verzeichnisstruktur sieht mit der Standardkonvention wie folgt aus:

︙
├── domain
│   ├── build.gradle
│   └── src
│       ├── main
│       │   └── java
│       │       └── de/cronn
│       │              └── CustomerEntity.java
│       ├── test
│       │   └── java
│       │       └── de/cronn
│       │              └── ModelTest.java
│       └── testFixtures
│           └── java
│               └── de/cronn
│                      └── TestDataFactory.java
└── exporters
    ├── build.gradle
    ︙

Abhängigkeiten der Test-Fixtures lassen sich über die bereitgestellten Konfigurationen testFixturesImplementation und testFixturesApi angeben. Diese verhalten sich wie die implementation und api Konfigurationen des java-library-Plugins.

Einbinden der Test-Fixtures als Abhängigkeit

Im letzten Schritt wird noch die Abhängigkeit im exporters-Projekt deklariert:

exporters/build.gradle:

dependencies {
    // ...
    testImplementation testFixtures(project(':domain'))
}

Mit diesem Setup kann die Klasse ExportTest aus dem exporters-Projekt nun auf die TestDataFactory aus dem domain-Projekt zugreifen.

Separate Projekte

Das Teilen der Test-Fixtures funktioniert nicht nur zwischen Gradle-Subprojekten, sondern auch bei separaten Projekten.

Abhängigkeiten werden dann über veröffentlichte Artefakte hergestellt. Mit dem java-test-fixtures Plugin ist keine weitere Konfiguration nötig. Es wird standardmäßig bereits ein weiteres Artefakt erzeugt und veröffentlicht:

mygroup
├── myproject
│   └── 1.0
│       ├── myproject-1.0-plain.jar
│       ├── myproject-1.0-sources.jar
│       ├── myproject-1.0-test-fixtures.jar
│       ├── myproject-1.0.module
│       └── myproject-1.0.pom

Mit dem Gradle-eigenen maven-publish Plugin werden zusätzliche Meta-Information im *.module-File abgelegt. 1

Damit lassen sich in dem zweiten Projekt die Abhängigkeiten sauber deklarieren:

dependencies {
    // ...
    testImplementation testFixtures("mygroup.myproject:1.0")
}

IDE-Unterstützung

Da das Gradle-Plugin auf bestehenden Techniken aufsetzt (SourceSet), ist die Unterstützung in den IDEs gut.

Bei der Suche nach Text und Symbolen bieten die IDEs in der Regel Filterfunktionen. So kann man die Suchergebnisse auf Test- oder Produktionscode einschränken. Die Test-Fixtures werden dabei unterschiedlich eingeordnet:

  • Für Eclipse zählen Test-Fixtures korrekterweise zum Testcode.
  • In IntelliJ werden die Test-Fixtures als Produktionscode angesehen. Wer die Zuordnung beim Filtern ändern möchte, kann sich eigene Scopes einrichten.

Footnotes

  1. Um die Benennung einfach und konsistent zu halten, empfehlen wir rootProject.name und artifactId auf denselben Wert zu setzen.


Paul-Fiete Hartmann

Paul-Fiete Hartmann