archunit 을 활용한 코드 컨벤션 검증

By deuk9

코드를 개발할 때, 로직의 정확성을 검증하는 단위 테스트(Unit Test)나 통합 테스트(Integration Test) 외에도 코드 자체에 대한 검증(Code Verification) 이 필요할 때가 있다. 이는 코드 컨벤션, 안티 패턴, 클래스 간의 의존성 등을 사전에 점검하여 유지보수성을 높이고, 프로젝트의 아키텍처를 일관되게 유지하는 데 유용하다.

이러한 코드 검증을 위해 ArchUnit 라이브러리를 활용할 수 있다. ArchUnit은 런타임 전에 코드를 분석하여 패키지 구조, 클래스 네이밍 규칙, 의존성 등을 체크할 수 있도록 도와주는 강력한 도구이다. 이 글에서는 ArchUnit을 이용해 코드 검증 테스트를 작성하는 방법을 살펴본다.


1. ArchUnit 의존성 추가

Gradle 프로젝트에서 ArchUnit을 사용하려면,build.gradle.kts또는build.gradle 파일에 다음과 같이 의존성을 추가한다.

testImplementation 'com.tngtech.archunit:archunit:1.1.0'

2. 코드 검증 테스트 작성

2.1 코드 컨벤션 체크

아래 테스트는 controller 패키지의 클래스들이 반드시 Controller로 끝나는 이름을 가져야 하며, @RestController 어노테이션이 포함되어 있어야 한다는 규칙을 검증한다.

public class ArchitectureTest {  
    
    JavaClasses javaClasses;  
    
    @BeforeEach  
    void setup() {  
        javaClasses = new ClassFileImporter()  
                .withImportOption(new ImportOption.DoNotIncludeTests())  
                .importPackages("com.example.deuktest");  
    }  
    
    @Test  
    @DisplayName("Controller 패키지의 클래스는 Controller로 끝나야 한다.")  
    void controllerTest() {  
        ArchRule rule = classes()  
                .that().resideInAnyPackage("..controller")  
                .should().haveSimpleNameEndingWith("Controller");  
    
        ArchRule annotationRule = classes()  
                .that().resideInAnyPackage("..controller")  
                .should().beAnnotatedWith(RestController.class);  
    
        rule.check(javaClasses);  
        annotationRule.check(javaClasses);  
    }  
}

이 테스트가 통과하려면 controller 패키지 내의 모든 클래스가 Controller로 끝나야 하며, @RestController어노테이션이 있어야 한다. 이를 통해 일관된 네이밍 규칙을 유지할 수 있다.


2.2 의존성 체크

2.2.1 Controller는 ServiceRequestResponse 패키지의 클래스만 의존할 수 있다.

일반적으로 Controller는 비즈니스 로직을 처리하는 Service 계층과 사용자 요청을 받는 Request, 응답을 반환하는 Response 계층과만 의존하는 것이 좋다. 이를 ArchUnit으로 검증하는 코드를 작성하면 다음과 같다.

@Test  
@DisplayName("Controller는 Service와 Request/Response를 사용할 수 있다.")  
void controllerDependencyTest() {  
    ArchRule rule = classes()  
            .that().resideInAnyPackage("..controller")  
            .should().dependOnClassesThat()  
            .resideInAnyPackage("..service..", "..request..", "..response..");  
    
    rule.check(javaClasses);  
}

이 검증을 통해 controller 패키지의 클래스가 불필요한 의존성을 가지지 않도록 제한할 수 있다.

2.2.2 Controller는 Model을 직접 의존할 수 없다.

일반적으로 Model 객체(Entity)는 Service 계층에서 관리되어야 하며, Controller가 직접 Model을 참조하는 것은 바람직하지 않다. 이를 검증하는 테스트는 다음과 같다.

@Test  
@DisplayName("Controller는 Model을 사용할 수 없다.")  
void controllerCantHaveModelTest() {  
    ArchRule rule = noClasses()  
            .that().resideInAnyPackage("..controller")  
            .should().dependOnClassesThat().resideInAnyPackage("..model..");  
    
    rule.check(javaClasses);  
}

이 검증을 통해 Controller가 직접 Model을 참조하는 실수를 방지할 수 있다.


3. 마무리

이처럼 ArchUnit을 활용하면 코드의 동작을 테스트하는 것이 아니라 코드 자체의 구조와 규칙을 검증할 수 있다.

✅ 주요 활용 사례

  • 코드 컨벤션 체크 (클래스명, 어노테이션 사용 규칙 등)
  • 계층 간의 의존성 관리 (Controller -> Service -> Repository 등의 의존성 흐름 유지)
  • 특정 패키지나 클래스의 사용 제한 (예: Repository 패키지는 Controller에서 직접 호출 금지)
  • 안티 패턴 감지 및 코드 품질 향상

참고 자료