ArchUnit:Java架构测试的利器

153 阅读4分钟

文章首发公众号『风象南』

之前分享了一篇关于『Java项目工程分包』设计的文章,更近一步,随着代码规模扩张和模块复杂度提升,系统架构的腐化风险显著增加,如何持续保证系统整体代码结构的清晰与整洁,传统靠人工代码审查难以有效应对分层混乱、循环依赖、职责扩散等架构问题。

ArchUnit 作为轻量级架构治理工具,通过规则引擎和自动化检测机制,可以提供对代码结构和依赖关系的全面分析。它能够帮助开发团队快速发现潜在的架构违规问题,并在早期阶段进行修正,从而有效提升代码质量和系统可维护性。

ArchUnit 是什么

ArchUnit 是一个专为 Java 开发者打造的架构测试框架。它允许我们以代码的方式定义和验证 Java 项目的架构规则,确保代码库在不断演进的过程中始终遵循预先设定的架构规则。

ArchUnit 能做什么

1. 定义和验证分层架构规则

在常见的 Java 项目中,分层架构如表现层、业务逻辑层和数据访问层是非常普遍的。使用 ArchUnit,我们可以轻松定义各层之间的访问规则。

例如,规定表现层只能调用业务逻辑层的接口,而业务逻辑层不能直接访问数据访问层的实现类。

通过这样的规则定义,ArchUnit 能够在测试过程中检查代码是否严格遵守了分层架构的约束,防止层与层之间的非法依赖,从而保持架构的清晰性和稳定性。

2. 确保依赖关系的合理性

项目中类与类之间的依赖关系错综复杂,如果不合理控制,很容易形成难以维护的依赖网。ArchUnit 可以帮助我们定义和验证依赖规则。

比如,限制某个模块不能依赖特定的其他模块,或者确保某些核心模块只能被特定的模块依赖。这样能够有效避免不必要的依赖,减少模块之间的耦合度,提高代码的可维护性和可扩展性。

3. 验证代码结构规范

除了架构层面的规则,ArchUnit 还能用于验证代码的结构规范。例如,我们可以规定所有的实体类必须位于特定的包下,或者所有的服务类必须实现特定的接口。

ArchUnit 的优势

1. 无缝集成到测试流程

ArchUnit 与主流的 Java 测试框架如 JUnit、TestNG 具备较好的兼容性。可以像编写普通单元测试一样,将架构测试用例融入到现有的测试套件中。

2. 简洁易用的 DSL

ArchUnit 提供了一种简洁且易于理解的领域特定语言(DSL)来定义架构规则。能快速上手,根据项目的需求编写合适的架构测试用例。

3. 强大的灵活性

ArchUnit支持各种复杂的架构场景,可以根据项目的具体需求定制不同层次的架构规则,从基本的包依赖规则到复杂的类关系约束,可以根据需要灵活配置。

如何在项目中使用 ArchUnit

在项目的构建文件(如 Maven 的 pom.xml 或 Gradle 的 build.gradle)中添加 ArchUnit 的依赖。以 Maven 为例

 <dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit-junit5</artifactId>
    <version>0.21.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit</artifactId>
    <version>0.21.0</version>
    <scope>test</scope>
</dependency>

1. 编写架构测试用例

根据项目的架构设计,使用 ArchUnit 的 DSL 编写相应的架构测试方法。

3.1 验证分层架构

表现层不应直接调用数据访问层

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
public class LayeredArchitectureTest {
    @Test
    public void testLayeredArchitecture() {
        JavaClasses classes = new ClassFileImporter().importPackages("com.example.project");
        ArchRule rule = noClasses()
               .that().areInPackage("com.example.project.presentation")
               .should().callClassesThat().areInPackage("com.example.project.dataaccess");
        rule.check(classes);
    }
}
3.2 检查依赖关系

禁止 moduleA 中的类直接依赖 moduleC 中的类

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
public class DependencyRuleTest {
    @Test
    public void testDependencyRule() {
        JavaClasses classes = new ClassFileImporter().importPackages("com.example.project");
        ArchRule rule = noClasses()
               .that().areInPackage("com.example.project.moduleA")
               .should().dependOnClassesThat().areInPackage("com.example.project.moduleC");
        rule.check(classes);
    }
}
3.2 验证代码结构

强制所有以 Entity 结尾的类必须放在 entities 包中

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
public class CodeStructureTest {
    @Test
    public void testCodeStructure() {
        JavaClasses classes = new ClassFileImporter().importPackages("com.example.project");
        ArchRule rule = classes()
               .that().haveSimpleNameEndingWith("Entity")
               .should().beInPackage("com.example.project.entities");
        rule.check(classes);
    }
}

2. 执行架构测试

在项目构建过程中,运行测试任务(如 Maven 的mvn test命令或 Gradle 的gradle test命令),ArchUnit 会自动执行编写好的架构测试用例。

如果发现有违反架构规则的情况,测试将会失败,并给出详细的错误信息。

总结

ArchUnit 提供了一种强大而有效的方式来确保项目架构的质量和稳定性。

通过定义和验证架构规则,它能够在项目体积不断增长的过程中,及时发现潜在的架构问题,避免代码陷入混乱。