使用ArchUnit测试Java项目的体系结构

本文介绍如何使用ArchUnit测试库进行架构级别的测试,包括命名约定、类路径、方法返回类型、循环依赖及分层结构测试。通过具体示例,展示了如何发现并解决命名规范、依赖关系和层次结构等问题。

 

ArchUnit是一个很有用的测试库。ArchUnit不会测试代码流程或业务逻辑,而是用来测试“架构”,包括类依赖关系、循环依赖关系、层访问、命名约定和继承检查等。

下面我们将使用ArchUnit编写几个测试用例:

  • 循环依赖测试

  • 层访问测试

  • 课堂位置测试

  • 方法返回类型测试

  • 命名约定测试

我们将使用如下所示包结构的Java项目:

进行体系结构编写测试之前,我们约定不应从任何其他类或程序包访问控制器类。同时,从项目角度,我们约定控制器名称应以“ ... Controller”后缀结尾。

下来开始编写第一个测试,用于检查我们的命名约定。

命名约定测试

@RunWith(ArchUnitRunner.class) 
@AnalyzeClasses(packages = "com.test.controllers") 
public class NamingConventionTests {
 @ArchTest ArchRule controllers_should_be_suffixed = classes()
 .that().resideInAPackage("..controllers..")
 .should().haveSimpleNameEndingWith("Controller"); 
}

运行测试,可以看到测试通过:

ArchUnit有两种测试类型,其中之一就像上面的测试用例那样。另外,也可以使用JUnit的Test注释编写测试。将RunWith参数更改为JUnit4.class并删除AnalyzeClasses注释。

@RunWith(JUnit4.class) 
public class NamingConventionTests {
    @Test
    public void controllers_should_be_suffixed() {
        JavaClasses importedClasses = new ClassFileImporter().importPackages("com.test.controllers");
         ArchRule rule = classes() .that().resideInAPackage("..controllers..")
             .should().haveSimpleNameEndingWith("Controller");
         rule.check(importedClasses);
    } 
}

接着来看看如果使用不同的后缀会发生什么。试着改("Controller") 为 ("Ctrl")并运行:

看到测试出了异常:“java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] — Rule ‘classes that reside in a package ‘..controllers..’ should have a simple name ending with ‘Ctrl’’ was violated (1 time): the simple name of com.test.controllers.FirstController does not end with ‘Ctrl’ in (FirstController.java:0)”。

ArchUnit确实帮我们发现了命名规范的问题。

类路径测试

下来加入另一条规则,以确保具有repositories annotation的类应位于infrastructure包里。

@RunWith(ArchUnitRunner.class) 
@AnalyzeClasses(packages = "com.test") 
public class RepositoryPackageTest { 
    @ArchTest 
    public ArchRule repositories_should_located_in_infrastructure = classes() 
        .that().areAnnotatedWith(Repository.class)
        .should().resideInAPackage("..infrastructure..");
}

如果我们为基础结构软件包以外的其他类加入repositories annotation,则测试将引发AssertionError。

方法返回类型测试

下来编写方法检查的测试用例。假设我们约定控制器类的方法应返回BaseResponse类型:

@RunWith(ArchUnitRunner.class) 
@AnalyzeClasses(packages = "com.test.controllers") 
public class ControllerMethodReturnTypeTest { 
    @ArchTest public ArchRule controller_public_methods_should_return = methods() 
        .that().areDeclaredInClassesThat().resideInAPackage("..controllers..")
        .and().arePublic() .should().haveRawReturnType(BaseResponse.class)         
        .because("here is the explanation"); 
}

循环依赖测试

首先创建具有循环依赖问题的类:

package com.test.services.slice1; 
import com.test.services.slice2.SecondService; 
public class FirstService { 
    private SecondService secondService; 
    public FirstService() { 
        this.secondService = new SecondService(); 
    } 
}

 

package com.test.services.slice2; 
import com.test.services.slice1.FirstService; 
public class SecondService { 
    private FirstService firstService; 
    public SecondService() { 
        this.firstService = new FirstService(); 
    } 
}

FirstService和SecondService相互依赖,存在循环依赖问题。

使用ArchUnit来编写一个测试用例:

@RunWith(ArchUnitRunner.class) 
@AnalyzeClasses(packages = "com.test") 
public class CyclicDependencyTest { 
    @ArchTest 
    public static final ArchRule rule = slices().matching("..services.(*)..") 
    .should().beFreeOfCycles(); 
}

运行此测试将得出以下结果:

分层结构测试

下来通过ArchUnit来编写覆盖各层的分层结构测试。

@RunWith(JUnit4.class) 
public class LayeredArchitectureTests { 
    @Test public void layer_dependencies_are_respected() { 
        JavaClasses importedClasses = new ClassFileImporter().importPackages("..com.test.."); 
        ArchRule myRule = layeredArchitecture() 
            .layer("Controllers").definedBy("..com.test.controllers..") 
            .layer("Services").definedBy("..com.test.services..") 
            .layer("Infrastructure").definedBy("..com.test.infrastructure..") 
            .whereLayer("Controllers").mayNotBeAccessedByAnyLayer() 
            .whereLayer("Services").mayOnlyBeAccessedByLayers("Controllers") 
            .whereLayer("Infrastructure").mayOnlyBeAccessedByLayers("Services");     
        myRule.check(importedClasses); 
    } 
}

下面的代码违反了上述规则,代码把service层的类对象注入到存储层了。

package com.test.infrastructure; 
import com.test.services.SecondService; 
public class FirstRepository { 
    SecondService secondService; 
    public FirstRepository(SecondService secondService) { 
        this.secondService = secondService; 
    } 
}

运行测试时,看到的repository层代码存在违反约定的问题:

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值