어플리케이션의 아키텍처를 테스트 할 수 있는 오픈 소스 라이브러리로 패키지, 클래스, 레이어, 슬라이스 간 의존성을 확인할 수 있는 기능을 제공한다.
아키텍처 테스트 유스 케이스(Circular Dependancy 확인)
circular dependency가 있으면 코드 분석시 일관된 흐름이 없어 코드 파악 및 분석이 어렵기에 다음의 과정들을 통해 이를 찾아내고 제거해야 함
A라는 패키지가 B (또는 C, D) 패키지에서만 사용되고 있는지 확인 가능
Service라는 이름의 클래스들이 Controller 또는 Service라는 이름의 클래스에서만 참조하고 있는지 확인
Service라는 이름의 클래스들이 ..service..라는 패키지에 들어있는지 확인
A라는 어노테이션을 선언한 메소드만 특정 패키지 또는 특정 어노테이션을 가진 클래스를 호출하고 있는지 확인
특정 스타일의 아키텍처를 따르고 있는지 확인
JUnit5용 ArchUnit 설치
<dependency>
<groupId>com.tngtech.archunut</groupId>
<artifactId>archunit-junit5-engine</artifactId>
<version>0.12.0</version>
<scope>test</scope>
</dependency>
주요 사용법
@Test
public void Services_should_only_be_accessed_by_Controllers(){
JavaClasses importedClasses= new ClassFileImporter().importPackages("com.mycompany.myapp");
//확인할 클래스 읽어들임
ArchRule myRule= classes()
.that().resideInAPackage("..service..")
.should().onlyBeAccessed().byAnyPackage("..controller..","..service..");
//룰 정의
myRule.check(importedClasses);
//룰을 잘 따르는지 확인
}
JUnit5 확장팩 제공
확인하려는 패키지 구조(순환 참조 없는 상태)
확인
..domain.. 패키지에 있는 클래스는 ..study.., ..member.., ..domain에서 참조 가능
..member.. 패키지에 있는 클래스는 ..study..와 ..member..에서만 참조 가능
반대로 ..domain.. 패키지는 ..member.. 패키지를 참조하지 못함
..study.. 패키지에 있는 클래스는 ..study..에서만 참조 가능
순환 참조 없어야 함
ArchUnit이 제공하는 API를 직접 사용해 테스트 코드 작성
public class ArchTests{
@Test
void packageDependencyTests(){
JavaClasses classes= new ClassFileImporter().importPackages("me.always.archtest");
ArchRule domainPackageRule= classes().that().resideInAPackage("..domain..")
.should().onlyBeAccessed().byClassesThat().resideInAnyPackage("..study..",",,member..","..domain..");
domainPackageRule.check(classes);
//domain이라는 키워드가 들어있는 패키지들은 study, member, domain이라는 키워드가 있는 패키지만 참조할 수 있다
ArchRule memberPackageRule= noClasses().that().resideInAPackage("..domain..")
.should().accessClassesThat().resideInAPackage("..member..");
memberPackageRule.check(classes);
//domain이라는 키워드의 패키지 안에 있는 어떠한 클래스도 member라는 키워드를 가진 패키지 안에 있는 클래스에 접근할 수 없다
ArchRule studyPackageRule= noClasses().that().resideOutsideOfPackage("..study..")
.should().accessClassesThat().resideInAPackage("..study..");
studyPackage.check(classes);
//study라는 키워드의 패키지에 study라는 키워드의 패키지가 아닌 다른 패키지가 접근할 수 없다
ArchRule freeOfCycles= slices().matching("..archtest.(*)..")
.should().beFreeOfCycles();
freeOfCycles.check(classes);
//순환 참조 제거
}
}
ArchUnit이 제공하는 JUnit 확장팩 사용
@AnalyzeClasses: 클래스를 읽어들여 확일할 패키지 설정
@Archtest: 확인할 규칙 정의
@AnalyzeClasses(packagesOf= App.class)
public class ArchTests{
@ArchTest
ArchRule domainPackageRule= classes().that().resideInAPackage("..domain..")
.should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..study..","..member..","..domain..");
@ArchTest
ArchRule memberPackageRule= noClasses().that().resideAPackage("..domain..")
.should().accessClassesThat().resideInAPackage("..member..");
@ArchTest
ArchRule studyPackageRule= noClasses().that().resideOutsideOfPackage("..study..")
.should().accessClassesThat().resideInAnyPackage("..study..");
@ArchTest
ArchRule freeOfCycles= slices().matching("..archtest.(*)..")
.should().beFreeOfCycles();
}
코드의 가독성은 좋아지나, 디스플레이 이름을 따로 줄 수는 없음
확인하려는 클래스 의존성
테스트 내용
@AnalyzeClasses(packageOf= App.class)
public class ArchClassTests{
@ArchTest
ArchRule controllerClassRule= classes().that().haveSimpleNameEndingWith("Controller")
.should().accessClassesThat().haveSimpleNameEndingWith("Service")
.orShould().accessClassesThat().haveSimpleEndingWith("Repository");
//이름이 Controller로 끝나는 클래스들은 이름이 Service와 Repository로 끝나는 클래스들을 참조할 수 있다.
@ArchTest
ArchRule repositoryClassRule= noClasses().that().haveSimpleEndingWith("Repository")
.should().accessClassesThat().haveSimpleNameEndingWith("Service");
//Repository로 끝나는 클래스들은 Service로 끝나는 클래스들을 사용할 수 없다
@ArchTest
ArchRule studyClassesRule= classes().that().javeSimpleNameStartingWith("Study")
.and().areNotEnums()
.and().areNotAnnotatedWith(Entity.class)
.should().resideInAnyPackage("..study..");
//Study로 시작하는 클래스이고, enum이 아니고 entity라는 어노테이션을 가지고 있지 않은 클래스들은 반드시 Study 패키지 안에 있어야 한다.
}