ArchUnit
Einleitung
ArchUnit ist eine kostenlose, einfache und erweiterbare Bibliothek zum Überprüfen der Architektur des Java-Codes. Das Framework kann Abhängigkeiten zwischen Software-Komponenten, Klassen und Methoden überprüfen, nach zyklischen Abhängigkeiten suchen, falsche oder fehlende Aufrufe identifizieren, Annotations checken, Codierungskonventionen überprüfen und vieles mehr. Dazu analysiert es den Java-Bytecode und importiert alle Klassen in eine Java-Objektstruktur. Die selbst entwickelten oder vom Framework bereitgestellten Architektur-Regeln werden mittels einer gut lesbare Fluent-API definiert und können in beliebigen Unittest-Frameworks wie z.B. JUnit eingebunden und ausgeführt werden.
Aufbau
ArchUnit ist grundsätzlich in drei Schichten unterteilt. Basis ist der Core-Layer, der den Import der Klassen aus dem Bytecode übernimmt, diese analysiert und die interne Datenstruktur aufbaut. Die höheren Schichten von ArchUnit arbeiten auf dieser Struktur, sie kann aber auch für die Implementierung eigener Funktionalitäten genutzt werden.
Vorwiegend werden die Architekturregeln aber mit der Fluent-API des Lang-Layers ausgedrückt. Die aneinandergereihten Aufrufe bilden dabei einen lesbaren Satz, so dass Architekturregeln idealerweise selbsterklärend sind.
Darüber hinaus bietet der Library-Layer vordefinierte Architekturen, Regeln, Metriken und weitere Werkzeuge. Einige Funktionen dieser API werden hier im Kapitel „Library-Layer“ vorgestellt.
Lang Layer
Zur Demonstration von ArchUnit soll hier beispielhaft eine einfach strukturierte App eingeführt und getestet werden. Sie soll das Model-View-Control-Muster erfüllen. Das folgende Komponentendiagramm zeigt die dafür zugelassenen Abhängigkeiten der Komponenten:
Um die korrekte Umsetzung der App zu prüfen, definiert der folgende JUnit5-Test eine Architekturregel testModelDependencies
. Sie stellt über die Fluent-API des Lang-Layers wortwörtlich sicher, dass „Klassen aus Package Model nur Java eigene Klassen und Klassen aus Package Model referenzieren dürfen, da Model-Klassen unabhängig sein sollen“:
Über die Annotation @AnalyzeClasses
wird der Rahmen festgelegt, welche Klassen von ArchUnit zu importieren sind. Das sind hier konkret alle Klassen aus Package de.eckcellent_it
inklusive der Unterpackages. Die Testausführung zeigt eine Verletzung der Architekturregel:
ArchUnit berichtet, dass die Klasse Person
aus dem Package Model
eine Methode der Utils
-Klasse aufruft, die sich in Package View
befindet, was der Vorgabe widerspricht. Zum besseren Verständnis
enthält die Fehlermeldung dabei auch genau die Formulierung der Regel, wie sie in der Fluent-API zusammengesetzt wurde.
Library-Layer
Die oben gezeigte Regel unseres Beispiels prüft nur die Abhängigkeiten der Komponente Model
. Besser wäre aber die Prüfung aller Komponenten auf unerwünschte Abhängigkeiten.
Die Library-API von ArchUnit bietet da mehrere Möglichkeiten, zum Beispiel die Nutzung vordefinierter Architekturen. Der folgende Test nutzt die LayeredArchitecture
-Regel, um die Beziehungen aller Komponenten untereinander zu prüfen:
Wie erwartet, meldet der LayeredArchitecture
-Test den bereits bekannten Fehler. Alternativ unterstützt ArchUnit auch das Einlesen von PlantUML-Komponentendiagrammen. Somit kann das Komponenten-Diagram von oben genutzt werden, um sowohl die Zusammenhänge der Komponenten zu dokumentieren als auch zu testen. Diese Test-Variante meldet ebenfalls den erwarteten Abhängigkeitsfehler:
Eine weitere Möglichkeit ist die Definition von „Slices“, um zum Beispiel unerwünschte zyklischen Abhängigkeiten zu erkennen. Slices sind Code-Cluster, die jeweils einen fachlichen Zusammenhang ausdrücken
und Teile mehrerer Software-Schichten und -Komponenten umfassen können.
Der folgende Test definiert jede Komponente der Beispiel-App jeweils als Slice und prüft, ob ihre Abhängigkeiten untereinander einen Zyklus bilden. Die bereits zuvor festgestellte Regelverletzung der Beispiel-App verursacht einen solchen Zyklus, so dass dieser Test ebenfalls fehlschlägt:
Der Library-Layer bietet darüber hinaus noch viele weitere Funktionen, unter anderem Metrik-Messungen, weitere vordefinierte Regeln und Architekturen sowie das Speichern und Tolerieren bekannter Regelverstöße (Freezing), was die Integration von ArchUnit in bestehende Projekte vereinfacht.
Erweiterbarkeit von ArchUnit
Die Fluent-API von ArchUnit ermöglicht die Definition weiterer Regeln, die über die Beziehung von Komponenten hinaus gehen. So können unter anderem Regeln für Sichtbarkeiten, Vererbungsstrukturen, Benamungen und erforderliche Annotations angelegt werden.
Das obere Beispiel zeigt eine einfache Regel die prüft, ob alle Klassen im Model
-Package public
sind und die @Entity
-Annotation besitzen. Sollen jedoch komplexere Zusammenhänge geprüft werden, die sich nicht ohne weiteres über die Fluent-API ausdrücken lassen, können eigene Prüfalgorithmen erstellt werden, die in Architekturregeln mit eingebunden werden. Dabei lassen sich Basisfunktionalitäten aus dem Core-Layer von ArchUnit wiederverwenden.
Im folgenden Beispiel soll geprüft werden, ob alle nicht statischen Felder von Model-Klassen eine @Column(name = "…")
-Annotation besitzen, wobei der Wert für name
dem Feld-Namen in Kleinschreibweise entsprechen soll, so wie in der folgenden Abbildung exemplarisch dargestellt:
Um dies zu realisieren, wird ein generischer Ausdruck in der Fluent-API geschrieben, der zwei Platzhaltern enthält: „fields().that($x).should($y)
“. Für $x erstellen wir eine Funktion areEntityMembers()
,
die eine DescribedPredicate
-Instanz liefert, um relevante Felder herauszufiltern. Dazu werden existieren Bausteine aus dem Core-Layer von ArchUnit wiederverwendet. Die beAnnotatedCorrectly()
Funktion für $y liefert eine ArchCondition
-Instanz, um Regelverstöße zu erkennen und zu sammeln. Andere komplexe Regelprüfungen lassen sich in gleicher Weise realisieren.
Fazit
ArchUnit ermöglicht die Dokumentation und die automatische Prüfung von Software-Architekturregeln. Das freie Java-Framework ist leicht in existierende Software-Projekte zu integrieren und kann durch die verständliche API schnell erlernt und in kurzer Zeit zu ersten Ergebnissen führen.
Die in dieser Einführung vorgestellten Funktionen repräsentieren nur einen kleinen Teil der Möglichkeiten von ArchUnit. Einen weiteren Überblick bietet der User Guide auf https://www.archunit.org.
Quellen
- Peter Gafert: Unit test your Java architecture https://www.archunit.org/
- Roland Weisleder: Unit Test Your Java Architecture With ArchUnit https://www.youtube.com/watch?v=ef0lUToWxI8
- Thomas Much: ArchUnit – Unit Testing Architecture and Design | JCON 2020 https://www.youtube.com/watch?v=K3AvAVpxhx0
- Eberhard Wolff: Peter Gafert zu ArchUnit https://www.youtube.com/watch?v=XgVlEagYA_w
- Philippe Sevestre, Bruno Fontana: Introduction to ArchUnit https://www.baeldung.com/java-archunit-intro
- Java Puzzle: ArchUnit: Unit test your Java architecture Spring Boot Example https://www.youtube.com/watch?v=6R8ghUojHHI
Softwareentwicklung