介绍
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