ArchUnit 소개
-
애플리케이션의 아키텍처를 테스트할 수 있는 오픈 소스 라이브러리로, 패키지, 클래스, 레이어, 슬라이스 간의 의존성을 확인할 수 있는 기능 제공
- 아키텍처 테스트 유즈 케이스
- A라는 패키지가 B 또는 C, D 패키지에서만 사용되고 있는지 확인 가능
- *Service라는 이름의 클래스들이 *Controller 또는 *Service라는 이름의 클래스에서만 참조하고 있는지 확인
- *Service라는 이름의 클래스들이 ..service.. 라는 패키지에 들어있는지 확인
- A라는 애노테이션을 선언한 메소드만 특정 패키지 또는 특정 애노테이션을 가진 클래스를 호출하고 있는지 확인
- 특정한 스타일의 아키텍처를 따르고 있는지 확인
- 참고
- https://www.archunit.org/
- https://blogs.oracle.com/javamagazine/unit-test-your-architecture-with-archunit
- https://www.archunit.org/userguide/html/000_Index.html
- moduliths: https://github.com/odrotbohm/moduliths
ArchUnit 설치
- 참고: https://www.archunit.org/userguide/html/000_Index.html#_junit_5
- dependency
<dependency> <groupId>com.tngtech.archunit</groupId> <artifactId>archunit-junit5-engine</artifactId> <version>0.12.0</version> <scope>test</scope> </dependency>
- 주요 사용법
- 특정 패키지에 해당하는 클래스를 (바이트코드를 통해)읽어들이고
- 확인할 규칙을 정의하고
- 읽어들인 클래스들이 그 규칙에 잘 따르는지 확인
@Test void services_should_only_be_accessed_by_controllers() { // 특정 패키지에 해당 클래스를 바이트 코드를 통해 읽어들이고 JavaClasses importedClasses = new ClassFileImporter() .importPackages("com.study.study"); // 확인할 규칙 정의 ArchRule archRule = ArchRuleDefinition.classes() .that().resideInAPackage("..service..") .should().onlyBeAccessed().byAnyPackage("..controller..", "..service.."); // 읽어들인 클래스들이 그 규칙을 잘 따르고 있는 지 확인 archRule.check(importedClasses); }
- JUnit 5 확장팩 제공
- @AnalyzeClasses: 클래스를 읽어들여서 확인할 패키지 설정
- @ArchTest: 확인할 규칙 정의
패치지 의존성 확인하기
- 예제
- Study -> Member
- Study -> Domain
- Member -> Domain
- 실제로 그런지 확인하려면
- ..domain.. 패키지에 있는 클래스는 ..study.., ..member.., ..domain..에서 참조 가능
- ..member.. 패키지에 있는 클래스는 ..study..와 ..member..에서만 참조 가능
- 반대로, ..domain.. 패키지는 ..member.. 패키지를 참조하지 못한다.
- ..study.. 패키지에 있는 클래스는 ..study.. 에서만 참조 가능
- 순환 참조가 없어야 한다.
@Test void packageDependencyTest() { JavaClasses classes = new ClassFileImporter().importPackages("com.study"); // ..domain.. 패키지에 있는 클래스는 ..study.., ..member.., ..domain..에서 참조 가능 ArchRule domainPackageRule = ArchRuleDefinition.classes() .that().resideInAPackage("..domain..") .should().onlyBeAccessed().byAnyPackage("..study..", "..member..", "..domain.."); domainPackageRule.check(classes); // ..domain.. 패키지는 ..member.. 패키지를 참조하지 못한다. ArchRule memberPackageRule = ArchRuleDefinition.noClasses() .that().resideInAPackage("..domain..") .should().accessClassesThat().resideInAPackage("..member.."); memberPackageRule.check(classes); // ..study.. 패키지에 있는 클래스는 ..study.. 에서만 참조 가능 ArchRule studyPackageRule = ArchRuleDefinition.noClasses() .that().resideOutsideOfPackage("..study..") .should().accessClassesThat().resideInAPackage("..study.."); studyPackageRule.check(classes); // 순환 참조가 없어야 한다. ArchRule freeOfCycles = SlicesRuleDefinition.slices().matching("com.study.(*)..") .should().beFreeOfCycles(); freeOfCycles.check(classes); }
JUnit 5 연동하기
- @AnalyzeClasses: 클래스를 읽어들여서 확인할 패키지 설정
- @ArchTest: 확인할 규칙 정의
// Application.class가 있는 패키지 선택
@AnalyzeClasses(packagesOf = Application.class)
public class ArchTests {
// ..domain.. 패키지에 있는 클래스는 ..study.., ..member.., ..domain..에서 참조 가능
@ArchTest
ArchRule domainPackageRule = ArchRuleDefinition.classes()
.that().resideInAPackage("..domain..")
.should().onlyBeAccessed().byAnyPackage("..study..", "..member..", "..domain..");
// ..domain.. 패키지는 ..member.. 패키지를 참조하지 못한다.
@ArchTest
ArchRule memberPackageRule = ArchRuleDefinition.noClasses()
.that().resideInAPackage("..domain..")
.should().accessClassesThat().resideInAPackage("..member..");
// ..study.. 패키지에 있는 클래스는 ..study.. 에서만 참조 가능
@ArchTest
ArchRule studyPackageRule = ArchRuleDefinition.noClasses()
.that().resideOutsideOfPackage("..study..")
.should().accessClassesThat().resideInAPackage("..study..");
// 순환 참조가 없어야 한다.
@ArchTest
ArchRule freeOfCycles = SlicesRuleDefinition.slices().matching("com.study.(*)..")
.should().beFreeOfCycles();
}
클래스 의존성 확인하기
- Study 패키지 안에 확인하려는 클래스 의존성
- StudyController -> StudyRepository
- StudyController -> StudyService
- StudyService -> StudyRepository
- 테스트 할 내용
- StudyController는 StudyService와 StudyRepository를 사용할 수 있다.
- Study*로 시작하는 클래스는 ..study.. 패키지에 있어야 한다.
- domain에 포함되어 있는, Entity, Enum 은 제외
-
StudyRepository는 StudyService와 StudyController를 사용할 수 없다.
- 코드
@AnalyzeClasses(packagesOf = Application.class) public class ArchStudyTests { // StudyController는 StudyService와 StudyRepository를 사용할 수 있다. @ArchTest ArchRule controllerClassRule = ArchRuleDefinition.classes() .that().haveSimpleNameEndingWith("Controller") .should().accessClassesThat().haveSimpleNameEndingWith("Repository") .orShould().accessClassesThat().haveSimpleNameEndingWith("Service"); // Study*로 시작하는 클래스는 ..study.. 패키지에 있어야 한다. @ArchTest ArchRule studyClassPackageRule = ArchRuleDefinition.classes() .that().haveSimpleNameStartingWith("Study") .and().areNotEnums() .and().areNotAnnotatedWith(Entity.class) .should().resideInAPackage("..study.."); // StudyRepository는 StudyService와 StudyController를 사용할 수 없다. @ArchTest ArchRule repositoryClassRule = ArchRuleDefinition.noClasses() .that().haveSimpleNameEndingWith("Repository") .should().accessClassesThat().haveSimpleNameEndingWith("Service") .orShould().accessClassesThat().haveSimpleNameEndingWith("Controller"); }
다양한 방법 정리
- Selenium WebDriver
- 웹 브라우저 기반 자동화된 테스트 작성에 사용할 수 있는 툴
- https://www.selenium.dev/projects/
- DBUnit
- 데이터베이스에 데이터를 CVS, Excel 등으로 넣어주는 툴
- http://dbunit.sourceforge.net/
- REST Assured
- REST API 테스트 라이브러리
- https://rest-assured.io/
- Cucumber
- BDD를 지원하는 테스트 라이브러리.
- https://cucumber.io/
** 참조: 백기선님 더 자바 애플리케이션을 테스트하는 다양한 방법 강의