Archunit testy architektury
Do pisania testów raczej nikogo nie trzeba przekonywać. Wielu z Was pisze testy jednostkowe, integracyjne, funkcjonalne. Ale jak można przetestować architekturę i czy to ma sens? Przy pomocy archunit spróbuję odpowiedzieć na te pytania.
Dlaczego
Archunit jest biblioteką którą za pomocą testów można opisać reguły dla architektury naszej aplikacji. Biorąc pod uwagę, że architektura cały czas żyje i się zmienia, to posiadanie jej opisu jako testy może być najlepszą dokumentacją. Dokumentacją, która jest cały czas aktualna i szybko wskaże reguły, które zostały złamane. Także, zbiór jasnych zasad pozwoli oszczędzić trochę czasu podczas code review.
Start
Najlepiej korzystanie z archunit zacząć od dokumentacji. Biblioteka posiada jary zgodne z junit5 oraz junit 4. Ale nic nie stoi na przeszkodzie aby używać jej również z innymi frameworkami do testów. Kolejnym plusem tego narzędzia jest DSL w jakim testy są napisane oraz łatwość ich czytania. Również ważne jest użycie klas, które opakowują refleksje w javie.
Archunit praktyka
Najłatwiej z biblioteki zacząć poprzez praktyczny przykład. Wobec tego na podstawie architektury portów i adapterów zademonstruję kilka reguł. Przykładowy projekt wygląda następująco:

Pierwszy test może wyglądać następująco:
@AnalyzeClasses(packages = "pl.socodeit.archunit") public class ArchitectureTest { @ArchTest static final ArchRule should_class_with_name_controller_should_be_in_adapter_primary_package = classes().that().haveSimpleNameContaining("Controller") .should().resideInAPackage("..adapter.primary.."); }
W powyższym fragmencie istnieją trzy ciekawe elementy. Pierwszym z nich jest @AnalyzeClasses. Ta anotacja określa jakie klasy, pakiety będą brane pod uwagę podczas analizy. Drugim jest @ArchTest, który określa nasze reguły. Trzecim elementem jest sposób zapisu zasady. Jest to statyczne finalne pole, które jest zainicjalizowany dsl. Nic nie stoi na przeszkodzie aby regułę napisać w formie metody testowej.
@ArchTest static void core_elements_should_not_depens_from_adapter(JavaClasses classes) { ArchRule rule = noClasses().that().resideInAPackage("..core..") .should().dependOnClassesThat().resideInAPackage("..adapter.."); rule.check(classes); }
Dsl z którego korzysta archunit posiada wiele możliwości zapisu reguł. Łatwo mogę stworzyć reguły, które mówią że w jakimś pakiecie istnieją tylko interfejsy. Kolejnymi mogą być ich implementacje a także klasa musi posiadać specyficzną anotację.
@ArchTest static final ArchRule secondary_port_should_be_interfaces = classes().that().resideInAPackage("..core.port.secondary..").should().beInterfaces(); @ArchTest static final ArchRule primary_port_should_implement_interface_from_secondary = classes().that().resideInAnyPackage("..adapter.secondary..").and() .haveSimpleNameContaining("Adapter") .should().implement(resideInAnyPackage("..core.port.secondary..")); @ArchTest static final ArchRule controllers_should_be_annotated_with_annotation = classes().that() .haveSimpleNameEndingWith("Controller").and().resideInAnyPackage( "..adapter.primary..").should().beAnnotatedWith(MyControllerAnnotation.class);
Z powyższych przykładów widać, że niektóre definicje reguł mogą się powtarzać. Dzięki archunit łatwo możesz poradzić sobie z takimi duplikatami.
@ArchTest static final ArchRule methods_in_domain_services_should_not_be_public = methods().that().areDeclaredInClassesThat(areServicesInPrimaryPort()) .should().notBePublic(); @ArchTest static final ArchRule fields_in_domain_services_should_private_and_final = fields().that().areDeclaredInClassesThat(areServicesInPrimaryPort()) .should().haveModifier(JavaModifier.FINAL).andShould().bePrivate(); private static DescribedPredicate<JavaClass> areServicesInPrimaryPort() { return packageForCorePrimaryPort().and(classIsService()); } private static DescribedPredicate<JavaClass> classIsService() { return JavaClass.Predicates.simpleNameEndingWith("Service"); } private static DescribedPredicate<JavaClass> packageForCorePrimaryPort() { return JavaClass.Predicates.resideInAPackage("..core.port.primary.."); }
The End
W powyższych przykładach pokazałem krótki wstęp do archunit. Dzięki bogatemu DSL łatwo możesz tworzyć swoje reguły dla systemu. Niektóre z nich mogą być proste a inne dużo bardziej skomplikowane. Najważniejszym punktem jest to, że Twoja architektura będzie się zmieniała w czasie ale wciąż każdy będzie znał jej zasady. W momencie gdy, ktoś te zasady złamie to czytelny sposób zostanie o tym poinformowany.
Kod dostępny na githubie.
Ło panie… w końcu po tylu latach jakaś sensowna alternatywa dla Sonara i podobnych narzędzi.
Na pewno uzupełnienie a czy alternatywa? Myślę, że w przyszłości jest na to duża szansa, zwłaszcza gdy stworzy się zbiór reguł pod różne typy klas i architektur.
Bardzo obiecujące Core API.
Jak długo trwają testy przeciętnej aplikacji?
Niestety nie testowałem tego na większej aplikacji więc trudno mi powiedzieć.