
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感兴趣,还应该查看全面的用户指南。
archunit