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