使用ArchUnit验证代码和体系结构约束

介绍

ArchUnit是一个用于根据一组自定义代码和体系结构约束检查Java代码的库。 这些约束可以在单元测试中的流畅Java API中定义。 ArchUnit可用于验证类或层之间的依赖关系,检查循环依赖关系等等。 在本文中,我们将创建一些示例规则,以了解如何从ArchUnit中受益。

必需的依赖

要使用ArchUnit,我们需要在项目中添加以下依赖项:

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

如果您仍在使用JUnit 4,则应改用archunit-junit4构件。

创建第一个ArchUnit规则

现在,我们可以开始创建第一个ArchUnit规则。 为此,我们在测试文件夹中创建一个新类:

 @RunWith (ArchUnitRunner. class ) //only for JUnit 4, not needed with JUnit 5  @AnalyzeClasses (packages = "com.mscharhag.archunit" )  public class ArchUnitTest { 
     // verify that classes whose name name ends with "Service" should be located in a "service" package 
     @ArchTest 
     private final ArchRule services_are_located_in_service_package = classes() 
             .that().haveSimpleNameEndingWith( "Service" ) 
             .should().resideInAPackage( "..service" );  } 

通过@AnalyzeClasses,我们告诉ArchUnit应该分析哪些Java软件包。 如果使用的是JUnit 4,则还需要添加ArchUnit JUnit运行器。

在类内部,我们创建一个字段并使用@ArchTest对其进行注释。 这是我们的第一个测试。

我们可以使用ArchUnits流畅的Java API定义要验证的约束。 在此示例中,我们要验证所有名称以Service结尾的类(例如UserService )都位于名为service (例如foo.bar.service )的包中。

大多数ArchUnit规则都以选择器开头,该选择器指示应验证哪种类型的代码单元(类,方法,字段等)。 在这里,我们使用静态方法classes()选择类。 我们使用that()方法将选择范围限制为类的子集(这里我们仅选择名称以Service结尾的类)。 使用should()方法,我们可以定义与所选类匹配的约束(此处:这些类应位于服务包中)。

运行此测试类时,将执行所有带有@ArchTest注释的测试。 如果ArchUnits在服务包之外检测到服务类,则测试将失败。

更多例子

让我们看一些更多的例子。

我们可以使用ArchUnit来确保所有Logger字段都是私有,静态和最终的:

 // verify that logger fields are private, static and final  @ArchTest  private final ArchRule loggers_should_be_private_static_final = fields() 
         .that().haveRawType(Logger. class ) 
         .should().bePrivate() 
         .andShould().beStatic() 
         .andShould().beFinal(); 

在这里,我们选择Logger类型的字段,并在一个规则中定义多个约束。

或者,我们可以确保实用程序类中的方法必须是静态的:

 // methods in classes whose name ends with "Util" should be static  @ArchTest  static final ArchRule utility_methods_should_be_static = methods() 
         .that().areDeclaredInClassesThat().haveSimpleNameEndingWith( "Util" ) 
         .should().beStatic(); 

为了强制名为impl的软件包不包含任何接口,我们可以使用以下规则:

 // verify that interfaces are not located in implementation packages  @ArchTest  static final ArchRule interfaces_should_not_be_placed_in_impl_packages = noClasses() 
         .that().resideInAPackage( "..impl.." ) 
         .should().beInterfaces(); 

请注意,我们使用noClasses()而不是classes()来抵消should约束。

(我个人认为,如果我们可以将规则定义为interfaces()。should()。notResideInAPackage(“ .. impl ..”),则该规则将更容易阅读。不幸的是,ArchUnit不提供interfaces()方法)

也许我们正在使用Java Persistence API并希望确保EntityManager仅在存储库类中使用:

 @ArchTest  static final ArchRule only_repositories_should_use_entityManager = noClasses() 
         .that().resideOutsideOfPackage( "..repository" ) 
         .should().dependOnClassesThat().areAssignableTo(EntityManager. class ); 

分层架构示例

ArchUnit还附带了一些实用程序,用于验证特定的体系结构样式。

例如,我们可以使用layeredArchitecture()来验证分层体系结构中各层的访问规则:

 @ArchTest  static final ArchRule layer_dependencies_are_respected = layeredArchitecture() 
         .layer( "Controllers" ).definedBy( "com.mscharhag.archunit.layers.controller.." ) 
         .layer( "Services" ).definedBy( "com.mscharhag.archunit.layers.service.." ) 
         .layer( "Repositories" ).definedBy( "com.mscharhag.archunit.layers.repository.." ) 
         .whereLayer( "Controllers" ).mayNotBeAccessedByAnyLayer() 
         .whereLayer( "Services" ).mayOnlyBeAccessedByLayers( "Controllers" ) 
         .whereLayer( "Repositories" ).mayOnlyBeAccessedByLayers( "Services" ); 

在这里,我们定义了三层:控制器,服务和存储库。 存储库层只能由服务层访问,而服务层只能由控制器访问。

通用规则的快捷方式

为了避免我们必须自己定义所有规则,ArchUnit附带了一组定义为静态常量的通用规则。 如果这些规则符合我们的需求,我们可以简单地将它们分配给测试中的@ArchTest字段。

例如,如果我们确保没有抛出Exception和RuntimeException类型的异常,则可以使用预定义的NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS规则:

 @ArchTest  private final ArchRule no_generic_exceptions = NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS; 

摘要

ArchUnit是一个强大的工具,可以根据一组自定义规则来验证代码库。 常见的静态代码分析工具(例如FindBugs或SonarQube)也报告了我们看到的一些示例。 但是,这些工具通常很难根据您自己的项目特定规则进行扩展,这就是ArchUnit的用武之地。

与往常一样,您可以从GitHub上的示例中找到Sources。 如果您对ArchUnit感兴趣,还应该查看全面的用户指南

翻译自: https://www.javacodegeeks.com/2020/02/validating-code-and-architecture-constraints-with-archunit.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以使用 ArchUnit 库来校验以"DTO"结尾的类是否只能被方法名以"Converter"结尾的方法作为入参和返回。以下是一个示例代码: ```java import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.library.Architectures; import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition; import com.tngtech.archunit.library.freeze.FreezingArchRule; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; public class DTOConverterArchTest { private static final String DTO_PACKAGE = "com.example.dto"; private static final String CONVERTER_PACKAGE = "com.example.converter"; public static final ArchRule dtoClassesShouldOnlyBeUsedByConverterMethods = classes() .that().resideInAPackage(DTO_PACKAGE) .should().onlyBeAccessed().byAnyPackage(CONVERTER_PACKAGE); public static final ArchRule converterMethodsShouldOnlyUseDTOClasses = classes() .that().haveSimpleNameEndingWith("Converter") .should().onlyHaveDependentClassesThat().resideInAnyPackage(DTO_PACKAGE); public static final ArchRule dtoClassesShouldOnlyBeUsedByConverterMethodsAsParametersOrReturnTypes = FreezingArchRule.freeze( SlicesRuleDefinition.slices().matching(DTO_PACKAGE + ".(*)..") .should().notDependOnEachOther()); public static void main(String[] args) { Architectures.LayeredArchitecture layeredArchitecture = Architectures.layeredArchitecture() .layer("DTO").definedBy(DTO_PACKAGE) .layer("CONVERTER").definedBy(CONVERTER_PACKAGE) .whereLayer("DTO").mayNotBeAccessedByAnyLayer() .whereLayer("CONVERTER").mayOnlyBeAccessedByLayers("DTO"); layeredArchitecture.check(dtoClassesShouldOnlyBeUsedByConverterMethods); layeredArchitecture.check(converterMethodsShouldOnlyUseDTOClasses); dtoClassesShouldOnlyBeUsedByConverterMethodsAsParametersOrReturnTypes.check(); } } ``` 以上代码定义了三个校验规则: 1. `dtoClassesShouldOnlyBeUsedByConverterMethods`:校验以"DTO"结尾的类只能被方法名以"Converter"结尾的方法访问。 2. `converterMethodsShouldOnlyUseDTOClasses`:校验方法名以"Converter"结尾的方法只能使用以"DTO"结尾的类。 3. `dtoClassesShouldOnlyBeUsedByConverterMethodsAsParametersOrReturnTypes`:校验以"DTO"结尾的类不应该相互依赖。 你可以根据实际情况,将包名 `com.example.dto` 和 `com.example.converter` 替换为你自己的包名

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值