Teknologi /
Derfor trenger du arkitekturtester
Det er noen år siden jeg først fikk øynene opp for arkitekturtesting. For meg ga det en helt ny måte å tenke rundt og jobbe med arkitektur på. I etterkant har jeg brukt det i alle prosjekter jeg har vært med på. Å lage noen enkle arkitekturtester koster lite, men kan ha stor verdi, spesielt over tid. Med innmarsjen av KI-agenter har arkitekturtesting blitt høyaktuelt.
Hvorfor trenger vi arkitekturtester?
Se for deg at et nytt system blir lagd. Det brukes mye tid og tankevirksomhet rundt arkitekturen og hvordan systemet skal utformes. Det utvikles av noen erfarne utviklere som har full kontroll på hva de driver med og er bevisste hvilke valg de tar.
I et slikt scenario lages det som regel gjennomtenkte løsninger som er rustet for at systemet skal kunne leve i flere år eller til og med flere tiår. Det er bare et lite problem. I løpet av levetiden til systemet vil det med overhengende sannsynlighet være utskiftninger av folk, omorganiseringer, videreutvikling, og behov for nye arkitekturbestemmelser eller endringer i arkitekturen for å møte nye krav.
Det er ofte her det glipper: når den sentrale arkitekten slutter eller teamet endrer sammensetning. Selv om man sørger for god kompetanseoverføring, er det vanskelig for nye som kommer inn i teamet å få nok oversikt over og eierskap til gjeldende arkitektur. Det kan også være vanskelig å forstå hva arkitekturbestemmelsene betyr i praksis, spesielt for uerfarne utviklere.
Selv om de pliktoppfyllende skriver en detaljert dokumentasjon som beskriver den overordnede arkitekturen, valgene som ble gjort og alle arkitekturprinsippene (det er jo ikke alltid det skjer, akkurat), så hjelper det i praksis lite. Problemet med en slik tradisjonell dokumentasjon er at den enten vil være lettlest og mangelfull, eller tunglest og utdatert. En stor og tunglest systemdokumentasjon vil i tillegg i beste fall bli skumlest, om den leses i det hele tatt.
Arkitekturtester lar oss holde den tradisjonelle systemdokumentasjonen minimal og overordnet. Vi kan heller skrive tester som sørger for å håndheve systemarkitekturen i praksis, og som i tillegg vil fungere som en levende dokumentasjon på hvilke prinsipper som gjelder.
Et annet typisk scenario er at et system starter smått og blir lagd for et begrenset og spesifikt formål. Etter hvert som bedriften vokser så blir også systemet utvidet med ny funksjonalitet, gjerne noe det i utgangspunktet ikke var designet for. Selv om systemet var lite og enkelt i starten, og hver endring isolert sett virker fornuftig, så vil stadige utvidelser over tid som regel erodere arkitekturen og gi en god dose teknisk gjeld. Avhengigheter ender ofte med å gå på kryss og tvers i koden, og det blir vanskelig å gjøre endringer fordi alt henger sammen med alt. På et tidspunkt blir den tekniske gjelden så stor at man må begynne å betale ned. I slike situasjoner kan arkitekturtester være til stor hjelp. Et system med for løs arkitektur kan strammes opp ved å skrive tester for hvordan man ønsker at systemet skal være, og deretter ta tak i alle feilene testene gir. På denne måten kan man nøste opp i floken steg-for-steg og sørge for bedre separation of concerns.
En annen grunn til å starte med arkitekturtester akkurat nå er innmarsjen av agentiske KI-assistenter som genererer kode. Ingen vet helt ennå hvordan utviklingen innen KI blir og i hvilken grad vi kommer til å bruke det i systemutvikling framover, men det er ingen tvil om at måten vi jobber på er i stor endring.
De beste kodeassistentene, som Claude Code, er allerede utrolige verktøy. Brukt riktig kan de bidra til bedre analyser, høyere kvalitet, økt testdekning og et mer effektivt utviklingsløp.
Likevel er de langt fra perfekte: de hallusinerer og løser problemer som ikke er der, gjør til tider grove feil, overfokuserer på detaljer og leverer ofte slurvete håndverk. Det blir utviklernes oppgave å få dem til å gjøre de riktige tingene, og å fange opp eventuelle feil.
Med økt bruk av KI forventes også et høyere utviklingstempo. Det setter utviklerne under større produksjonspress, og vil kreve mer kontekstbytting og mye mer kodegjennomgang. Da blir det også viktig å finne metoder for å holde fast ved arkitektur og kodestil.
Arkitekturtester som setter harde begrensninger på hva som er lov å gjøre, kan i den sammenheng være til stor nytte. Kodeassistentene kan selv kjøre testene og korrigere løsningen ut fra resultatet. Slik kan vi sørge for at arkitekturprinsippene holder seg over tid, og utviklerne slipper å bruke mye tid på å rette opp denne typen feil. Arkitekturtestene blir halsbåndet på en til tider litt overivrig KI-hund.
Kom i gang
For å skrive arkitekturtester kan vi bruke verktøy som ArchUnit. Kort og godt er ArchUnit et gratis javabasert bibliotek for å teste og håndheve arkitektur og kodestil. Den gjør det mulig å sjekke avhengigheter mellom pakker, klasser, metoder, lag og deler av systemet.
For å komme i gang må vi først ta inn ArchUnit som avhengighet i prosjektet. I dette eksemplet bruker vi Maven, men ArchUnit støtter også andre byggeverktøy.
1<dependency>
2 <groupId>com.tngtech.archunit</groupId>
3 <artifactId>archunit</artifactId>
4 <version>${archunit.versjon}</version>
5 <scope>test</scope>
6</dependency> I tillegg kan det være kjekt å legge til en utvidelse så ArchUnit samspiller med jUnit.
1<dependency>
2 <groupId>com.tngtech.archunit</groupId>
3 <artifactId>archunit- junit5</artifactId>
4 <version>${archunit.versjon}</version>
5 <scope>test</scope>
6</dependency>Når vi oppretter tester kan vi da bruke annotasjoner lignende som i jUnit.
1@AnalyzeClasses(
2 packages = ["no.kantega.**"],
3 importOptions = [ImportOption.DoNotIncludeTests::class]
4)
5class ArchitectureTest {
6 @ArchTest
7 fun myArchUnitTest(classes: JavaClasses) {
8 ( ....)
9 }
10} Noen kodeeksempler
Det er nesten bare fantasien som setter begrensninger for hva man kan skrive tester for. Her er noen av mine favoritter, skrevet i Kotlin.
Lagdelt arkitektur
En trenger ikke skrive haugevis av tester for å dra nytte av arkitekturtesting. En enkelt test kan gi mye verdi. Et eksempel på det er en test som sikrer en lagdelt arkitektur. Den er bare noen linjer lang, men hindrer at vi lager spagetti.
1@ArchTest
2fun `Dependencies should obey layered architecture`(classes: JavaClasses) {
3 layeredArchitecture()
4 .consideringAllDependencies()
5 .layer("api").definedBy("..api..")
6 .layer("service").definedBy("..service..")
7 .layer("adapters").definedBy("..adapters..")
8 .layer("persistence").definedBy("..persistence..")
9 .whereLayer("api").mayNotBeAccessedByAnyLayer()
10 .whereLayer("service").mayOnlyBeAccessedByLayers("api")
11 .whereLayer("adapters").mayOnlyBeAccessedByLayers("service")
12 .whereLayer("persistence").mayOnlyBeAccessedByLayers("service")
13 .check(classes)
14}Heksagonal arkitektur
Hvis en heller foretrekker heksagonal arkitektur eller “løkarkitektur”, så er det støtte for det også.
1@ArchTest
2fun `Dependencies should not violate hexagonal architecture`(classes: JavaClasses) {
3 onionArchitecture()
4 .domainModels("..domain.model..")
5 .domainServices("..domain.service..")
6 .applicationServices("..app..")
7 .adapter("database", "..adapter.database..")
8 .adapter("rest", "..adapter.rest..")
9 .adapter("kafka", "..adapter.kafka..")
10 .adapter("freg", "..adapter.freg..")
11 .check(classes)
12} Ingen sirkulære avhengigheter
Denne er enda enklere enn eksemplene over. Denne unngår spagetti også, men uten å måtte definere moduler. Her sjekkes det om det er noen sirkulære avhengigheter mellom pakkene på øverste nivå.
1@ArchTest
2fun `Packages should be free of cycles`(classes: JavaClasses) {
3 slices()
4 .matching("no.kantega.(*)..")
5 .should()
6 .beFreeOfCycles()
7 .check(classes)
8} Begrens hvor et rammeverk kan brukes
For oss som ikke er så begeistret for at rammeverk flyter inn i alle krinker og kroker av systemet, så kan en arkitekturtest begrense hvor de brukes. For eksempel kan vi begrense JPA til persistence-pakken.
1@ArchTest
2fun `JPA repositories should only exist in persistence package`(classes: JavaClasses) {
3 noClasses()
4 .that()
5 .areAssignableTo(Repository::class.java)
6 .should()
7 .resideOutsideOfPackage("..persistence..")
8 .check(classes)
9} Tving gjennom navnekonvensjon
Ofte er det en god idé å ha konsekvent navngivning, sånn at den kognitive lasten holdes nede og det blir lett å gjenkjenne konsepter. For eksempel kan vi lage en test som krever at alle kontrollerklassene heter noe som ender på “Controller”.
1@ArchTest
2fun `Rest controllers should be named controller`(classes: JavaClasses) {
3 classes()
4 .that()
5 .areAnnotatedWith(RestController::class.java)
6 .should()
7 .haveSimpleNameEndingWith("Controller")
8 .check(classes)
9} Sikkerhet først
Det kan være lett å glemme å legge på tilstrekkelig tilgangsstyringssjekker på nye endepunkt i API-et.
Hvis man for eksempel bruker PreAuthorize-annotasjon i Spring for å gjøre tilgangsstyringssjekker, kan vi lage oss en arkitekturtest som krever at den er til stede på alle endepunkter.
1@ArchTest
2fun `All endpoints should be secured with PreAuthorize`(classes: JavaClasses) {
3 methods()
4 .that()
5 .arePublic()
6 .and()
7 .areMetaAnnotatedWith(HttpMethod::class.java)
8 .should()
9 .beAnnotatedWith(PreAuthorize::class.java)
10 .check(classes)
11} Dette eksemplet er naturligvis ikke tilstrekkelig sikkerhetstesting alene, men kan fungere som et ekstra sikkerhetsnett.
Konklusjon
Arkitekturtester kan være et kraftig verktøy, og en god måte å dokumentere arkitekturen på uten å skrive lange avhandlinger som ingen leser. Med kodeassistenter kan det være ekstra nyttig for å sette rammene for utvikling. Håper du har blitt inspirert til å prøve.






