Softwareentwickler / Software Developer

ArchUnit | Überprüfen der Architektur in Java-Anwendungen

Ein wichtiger Bestandteil bei der Entiwcklung einer Anwendung ist das automatisierte Testen. Hierfür wurden verschiedenste Framworks entwickelt, um neben einfachen Unit-Tests noch weitere Komponenten untersuchen zu können, beispielsweise durch Mocking oder Stubbing. In diesem Artikel möchte ich eine Bibliothek vorstellen, mit der die gesamte Architektur einer Anwendung geprüft werden kann: ArchUnit.

Aufbau der Beispiel-Anwendung

Um beispielhaft zeigen zu können, wie ein solcher Test mit ArchUnit aussehen kann, möchte ich folgende Architektur als Grundlage nutzen. Es gibt die Schichten bzw. Packages:

  • Controller: Hauptsächlich für die Darstellung im Front-End. Auf die Controller wird aus dem JSF heraus zugegriffen.
  • Service: Die Controller nutzen Services, um Daten zu verarbeiten. Hier wird die Business-Logik abgebildet.
  • Repository: Ein Package das nur Interfaces beinhaltet. Diese Interfaces werden vom Service genutzt, um auf die Datenbank zuzugreifen.
  • Persistence: Hier befinden sich die Implementierungen der Interfaces. Über CDI werden diese zur Laufzeit werden diese zur Laufzeit in den Services genutzt.
  • Model: Dieses Package beinhaltet hauptsächlich Klassen, die zur Datenhaltung in der Anwendung genutzt werden.

Jede Schicht darf dabei nur die darunterliegenden kennen. Ausnahme dabei ist in diesem Fall, dass das Model die Persistence-Schicht für den Datenbankzugriff kennt, da ich das Domain und Persistence Model nicht voneinander getrennt habe.

Einsatz von ArchUnit

Unter anderem folgende Testfälle möchten wir jetzt kontrollieren:

  1. Klassen aus dem Package Controller werden aus keinem anderen Package heraus referenziert.
  2. Im Repository liegen nur Interfaces.
  3. Zugriff auf dieses Package geschieht nur aus dem Service und Persistence.
  4. Services werden nur aus den Controllern aufgerufen.
  5. Außerdem haben sie keinen direkten Zugriff auf die Implmentierungen im der Persistence-Schicht.

Um die Java-Klassen und -Pakete für den Test zu laden, bietet es sich an, eine Klasse dafür anzulegen.

public class Architektur
{
	protected JavaClasses klassen()
	{
		return new ClassFileImporter()
				.withImportOption(klasse -> !klasse.contains("Sollte")) // Testfälle sollen ignoriert werden
				.withImportOption(klasse -> !klasse.contains("IntegrationTest"))
				.importPackages("de.jonashellmann.anwendung..");
	}

	protected LayeredArchitecture architektur()
	{
		return Architectures.layeredArchitecture()
				.layer("Controller").definedBy("de.jonashellmann.anwendung.controller..")
				.layer("Domain").definedBy("de.jonashellmann.anwendung.domain..")
				.layer("Persistence").definedBy("de.jonashellmann.anwendung.persistence..")
				.layer("Repository").definedBy("de.jonashellmann.anwendung.repository..")
				.layer("Service").definedBy("de.jonashellmann.anwendung.service..");
	}
}

Anschließend können die Test-Cases folgendermaßen aussehen:

public class ControllerSollte extends Architektur
{
	@Test // Test 1
	public void keineEingehendenAbhaengigkeitenHaben()
	{
		architektur()
				.whereLayer("Controller")
				.mayNotBeAccessedByAnyLayer()
				.check(klassen());
	}
}
public class RepositorySollte extends Architektur
{
	@Test // Test 2
	public void nurInterfacesBeinhalten()
	{
		ArchRuleDefinition.classes().that().resideInAPackage("..repository..")
				.should().beInterfaces()
				.check(klassen());
	}

	@Test // Test 3
	public void nurVonServicesUndPersistenceVerwendetWerden()
	{
		architektur()
				.whereLayer("Repository")
				.mayOnlyBeAccessedByLayers("Service", "Persistence")
				.check(klassen());
	}
}
public class ServiceSollte extends Architektur
{
	@Test
	public void nurVonHoeherenSchichtenAufgerufenWerden()
	{
		architektur()
				.whereLayer("Service")
				.mayOnlyBeAccessedByLayers("Controller")
				.check(klassen());
	}


	@Test
	public void keinenZugriffAufPersistenceHaben()
	{
		ArchRuleDefinition.noClasses()
				.that().resideInAPackage("net.aokv.beitragsportal.service..")
				.should().accessClassesThat().resideInAPackage("net.aokv.beitragsportal.persistence..")
				.check(klassen());
	}
}

Fazit

Die Biblithek umfasst viele veschiedene Möglichkeiten, die Architektur zu testen. Vielleicht hilft dir das bei deinem nächsten Projekt ja weiter und du entdeckst noch andere Funktionalitäten von ArchUnit!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert