ArchUnit:轻松测试软件架构

为什么要测试你的架构?

当项目变得更大,架构变得更加复杂。每个项目都有开发人员需要遵循的标准规则。
新开发人员加入,他们可能会在不知情的情况下违反架构约束。如果每个人都在他们认为合适的地方添加新代码,每一个变化都可能对任何其他组件产生不可预见的影响,代码库就会变得混乱。

当然,您可以让一名或多名经验丰富的开发人员担任架构师的角色,他们每周查看一次代码,找出违规行为并加以纠正。

问题是这需要人工干预,有时我们并不能发现所有问题。保护软件架构免遭破坏的最佳方式是采用自动化流程。

在本文中,我将展示解决此类问题的 ArchUnit 框架。您将看到典型的实际示例,以了解如何将此工具集成到您的项目中。

什么是 ArchUnit

ArchUnit 用于单元测试 Java 项目架构。它可以检查包和类、层和切片之间的依赖关系,检查循环依赖关系等等。通常,开发人员会建立通用模式。
当有人违反规则时,测试将失败。开发人员将看到有关该问题的信息。它确保代码库保持完整,并且每个人都遵循准则。

ArchUnit 演示

我们创建一个 Spring Boot 项目,将 ArchUnit maven 依赖项添加到您的pom.xml:


    com.tngtech.archunit
    archunit
    1.0.1
    test


    com.tngtech.archunit
    archunit-junit5
    1.0.1
    test

我们创建一个 ArchUnitTest.java 的Java类,我们将把所有的测试都放在那里。

命名检查测试

如果您有多个模块,您可以使用注释 @AnalyzeClasses 指出要扫描的包:

@AnalyzeClasses(packages = "com.relive")

例如,您可能希望 SpringBoot 项目中 Application 类名称应为“SpringBootTestApplication”:

@ArchTest
    public static final ArchRule application_class_name_should_be =
            classes().that().areAnnotatedWith(SpringBootApplication.class)
                    .should().haveSimpleName("SpringBootTestApplication");

您可以检查是否所有 Controller 类都具有后缀“Controller”:

@ArchTest
    static ArchRule controllers_suffixed_should_be =
            classes().that().resideInAPackage("..controller..")
                    .or().areAnnotatedWith(RestController.class)
                    .should().haveSimpleNameEndingWith("Controller")
                    .allowEmptyShould(true);

包位置测试

您可能想检查实体类是否位于“entity”包中:

@ArchTest
    static final ArchRule tablename_must_reside_in_a_entity_package =
            classes().that().areAnnotatedWith(TableName.class)
                    .should().resideInAPackage("..entity..")
                    .as("TableName should reside in a package '..entity..'")
                    .allowEmptyShould(true);

同样,您可以检查配置类是否位于“config”包中:

@ArchTest
    static final ArchRule configs_must_reside_in_a_config_package =
            classes().that().areAnnotatedWith(Configuration.class)
                    .or().areNotNestedClasses()
                    .and().areAnnotatedWith(ConfigurationProperties.class)
                    .should().resideInAPackage("..config..")
                    .as("Configs should reside in a package '..config..'")
                    .allowEmptyShould(true);

注释测试

所有配置类都应具有 @Configuration 或者 @ConfigurationProperties 其中之一:

    @ArchTest
    static ArchRule configs_should_be_annotated =
            classes()
                    .that().resideInAPackage("..config..")
                    .and().areNotNestedClasses()
                    .should().beAnnotatedWith(Configuration.class)
                    .orShould().beAnnotatedWith(ConfigurationProperties.class);

图层测试

所有 Service 层的 Java 类仅可以被 Controller 层访问,Dao 层的 Java 类仅可以被 Service 层访问:

@ArchTest
    static ArchRule layer_inspection = layeredArchitecture()
            .consideringAllDependencies()
            .layer("Controller").definedBy("..controller..")
            .layer("Service").definedBy("..service..")
            .layer("Dao").definedBy("..dao..")

            .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
            .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
            .whereLayer("Dao").mayOnlyBeAccessedByLayers("Service");

测试排除

有时某些类我们不想执行规则,例如 JUnit 测试类。

这可以通过以下方式轻松实现:

@AnalyzeClasses(packages = "com.relive", importOptions = {ImportOption.DoNotIncludeTests.class})

或者有一个你想忽略的类,因为规则不适用于它。我们可以创建一个自定义规则并导入它,如下所示:

@AnalyzeClasses(packages = "com.relive", importOptions = {ArchUnitTest.ExcludeControllerImportOption.class})
public class ArchUnitTest {


    static class ExcludeControllerImportOption implements ImportOption {
        @Override
        public boolean includes(Location location) {
            return !location.contains("SomeExcludedControllerClasses");
        }
    }

}

结论

现在你已经了解如何将 ArchUnit 测试框架集成到您的 Java 项目中。您还熟悉了一些应用中的常用规则。

如果你想了解更多关于 ArchUnit 的规则示例,你可以参考 ArchUnit 用户指南 。
我想这里可以让你更加深入研究。

与往常一样,本文中使用的源代码可在 GitHub 上获得。

你可能感兴趣的:(ArchUnit:轻松测试软件架构)