Archunit i Spock

Jeśli nie wiesz, co to archunit, to przedstawiony kod powie więcej niż niejedna dokumentacja. Jeśli wiesz, ale zastanawiasz się, czy archunit zadziała z Spock to mój przykład udowodni Ci, że działa :)

ArchUnit


https://github.com/TNG/ArchUnit

TestRunner, który umożliwia tworzenie wygodnych i czytelnych asercji związanych z architekturą aplikacji. Genialne w swojej prostocie. To wspaniałe uczucie, kiedy build zakończy się błędem, bo developer nie zwrócił uwagi, że IDE właśnie upubliczniło jedną z wewnętrznych klas. Już nigdy nie zdarzy Ci się zapomnieć o ustaleniach projektowych z zespołem – co zaowocuje uspójnieniem kodu.

Spock

https://github.com/spockframework

Alternatywa dla JUNIT. Charakteryzuje się zgrabnym DSL testów. Dzięki językowi Groovy, testy są jeszcze bardziej zwięzłe i czytelne.

Archunit + Spock

Spock jest zgodny z JUNIT4, więc nie ma przeszkód, by dodać do testów specyfikację architektury w swoich pakietach.

Poniżej przedstawiam przykładowy zestaw testów, który ma za zadanie udokumentować zamiary programisty względem pakietu pl.example.dto

package pl.example

import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.tngtech.archunit.base.DescribedPredicate
import com.tngtech.archunit.core.domain.JavaClass
import com.tngtech.archunit.junit.AnalyzeClasses
import com.tngtech.archunit.junit.ArchTest
import com.tngtech.archunit.junit.ArchUnitRunner
import com.tngtech.archunit.lang.ArchCondition
import com.tngtech.archunit.lang.ArchRule
import com.tngtech.archunit.lang.ConditionEvents
import com.tngtech.archunit.lang.SimpleConditionEvent
import org.junit.runner.RunWith
import org.springframework.context.annotation.Configuration
import org.springframework.web.bind.annotation.RestController

import static com.tngtech.archunit.core.domain.Formatters.formatLocation
import static com.tngtech.archunit.core.domain.JavaModifier.FINAL
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes

@RunWith(value = ArchUnitRunner)
@AnalyzeClasses(packages = 'pl.example')
class PackageArchitectureSpec {

    @ArchTest
    public static final ArchRule DTO_SHOULD_BE_IMMUTABLE_AND_DESERIALIZABLE = classes()
            .that()
            .resideInAPackage('..dto..')
            .and().arePackagePrivate()
            .should().beAnnotatedWith(JsonDeserialize)
            .andShould().haveModifier(FINAL)
            .andShould(beImmutable())

    @ArchTest
    public static final ArchRule CONFIGURATION_SHOULD_BE_PACKAGE_PRIVATE = classes()
            .that()
            .areAnnotatedWith(Configuration)
            .or()
            .areAnnotatedWith(RestController)
            .should().bePackagePrivate()


    private static ArchCondition<JavaClass> beImmutable() {
        new ArchCondition<JavaClass>('be immutable') {
            @Override
            void check(JavaClass item, ConditionEvents events) {
                def mutableFieldsNames = item.getFields()
                        .findAll({ it -> !it.getModifiers().contains(FINAL) })
                        .collect({ it -> it.getName() })
                def message = String.format("class %s %s in %s",
                        item.getName(),
                        mutableFieldsNames.isEmpty() ?
                                "doesn't have mutable fields" :
                                "has mutable fields: $mutableFieldsNames",
                        formatLocation(item, 0))
                events.add(new SimpleConditionEvent(
                         item, 
                         mutableFieldsNames.isEmpty(), 
                         message))
            }
        }
    }
}

Zainspirowany postem, zgłosiłem PR z asercją beImmutable() https://github.com/TNG/ArchUnit/pull/86


Autor Bartosz Gałek

Tak jak Obelix nie potrzebował magicznego napoju do bycia silnym, tak Bartosz nie potrzebuje kawy do tego, aby tryskać energią na prawo i lewo i zarażać nią zespół, któremu przewodzi jako Team Leader w Allegro.
Prywatnie, ogromny fan gier planszowych, zwolennik i współtwórca otwartego oprogramowania oraz ruchu DevOps.

Leave a Reply