ArchUnit
testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
아키텍처 규칙을 테스트 코드로 검증하는 라이브러리로, "reservation은 cafe를 참조하면 안 된다"같은 약속을 JUnit 테스트로 작성해두면, 규칙을 어긴 코드가 생겼을 때 테스트가 실패하면서 실패한 위치를 알려준다.
일반 테스트와 다른 점
- 단위 테스트나 통합 테스트는 코드가 무엇을 하는지를 검증한다.
- 아키텍처 테스트는 코드가 어떻게 생겼는지를 검증한다. (어떤 패키지가 어떤 패키지를 참조하는지, 클래스 이름이 규칙에 맞는지 등)
- 기능이 정상 동작해도 아키텍처 테스트는 실패할 수 있다.
어떻게 검사하는지
- 컴파일된
.class파일을 읽어 클래스 간 의존 관계를 분석한다. 애플리케이션을 실행하지 않고 바이트코드만 보기 때문에 DB나 스프링 컨텍스트가 필요 없다. - 그래서
SpringBootTest를 붙일 필요가 없다. 붙여두면 테스트를 실행할 때마다 애플리케이션이 실행되면서 DB 연결까지 시도하므로 느려지기만 한다.
기본 문법
@AnalyzeClasses(packages = "com.studyhub")
public class ModuleDependencyTest {
@ArchTest
static final ArchRule reservation은_cafe와_member를_참조하지_않는다 =
noClasses()
.that().resideInAPackage("..reservation..")
.should().dependOnClassesThat(
resideInAPackage("..cafe..")
.or(resideInAPackage("..member..")));
@AnalyzeClasses(packages = ...: 분석할 최상위 패키지를 지정한다. 지정한 범위의 클래스들만 검사 대상이 된다.@ArchTest: 메스더가 아니라 필드에 붙이고 규칙 자체가 상태를 갖지 않는 값이기 때문에static final이어야 한다.noClasses(): "~한 클래스는 하나도 없어야 한다" 는 뜻이다.classes(): "~한 클래스는 모두 ~해야 한다" 는 뜻이다..that(): 검사 대상을 좁힌다..should(): 대상이 마족해야 할(또는 만족하면 안 되는) 조건을 쓴다...패키지명..: 앞뒤의..은 "그 아래 모든 하위 패키지"를 뜻한다...reservation..은reservation.domain,reservation.service등을 모두 포함한다.- 해석하면 "어떤 클래스도, reservation 패키지에 있으면서, cafe나 member 패키지의 클래스에 의존하면 안 된다"는 뜻이다.
전면 금지 : 메서드를 이어 붙이는 형태
noClasses()
.that().resideInAPackage("..reservation..")
.should().dependOnClassesThat().resideInAPackage("..cafe..");
dependOnClassesThat()을 호출한 뒤 점을 찍어.resideInAPackage(...)을 붙인다.- reservation은 cafe를 어떤 형태로도 참조하지 않으므로 이 형태로 충분하다.
일부 허용 : 조건을 인자로 넘기는 형태
noClasses()
.that().resideInAPackage("..cafe..")
.should().dependOnClassesThat(
resideInAPackage("..reservation..")
.and(not(resideInAPackage("..reservation.port..")))
);
- cafe는
reservation.port를 구현해야 하므로 reservation 전체를 막아서는 안 된다. - 조건을 조합하려면
dependOnClassesThat()의 괄호 안에 조건을 넣어야 한다. - 점을 찍는 형태로는
and,or,not을 쓸 수 없다. - 해석하면 "cafe에 있는 클래스는 reservation 패키지이면서 reservation.port는 아닌 클래스에 의존하면 안 된다"는 뜻이다.
and, or
resideInAPackage("..cafe..")
.and(resideInAPackage("..member.."))
and는 두 조건을 동시에 만족해야 한다는 뜻으로, 위의 예시는 "한 클래스가 cafe 패키지이면서 동시에 member 패키지"라는 뜻으로 항상 거짓이다.- 서로 다른 패키지를 나열할 때는
or, 한 패키지 안에서 범위를 좁힐 때는and를 사용한다.
테스트 코드 작성
@AnalyzeClasses(packages = "com.studyhub")
public class ModuleDependencyTest {
@ArchTest
static final ArchRule reservation은_cafe와_member를_참조하지_않는다 =
noClasses()
.that().resideInAPackage("..reservation..")
.should().dependOnClassesThat(
resideInAPackage("..cafe..")
.or(resideInAPackage("..member..")));
@ArchTest
static final ArchRule cafe는_reservation의_port만_참조한다 =
noClasses()
.that().resideInAPackage("..cafe..")
.should().dependOnClassesThat(
resideInAPackage("..reservation..")
.and(not(resideInAPackage("..reservation.port..")))
);
@ArchTest
static final ArchRule member는_reservation의_port만_참조한다 =
noClasses()
.that().resideInAPackage("..member..")
.should().dependOnClassesThat(
resideInAPackage("..reservation..")
.and(not(resideInAPackage("..reservation.port.."))));
@ArchTest
static final ArchRule member는_cafe의_port만_참조한다 =
noClasses()
.that().resideInAPackage("..member..")
.should().dependOnClassesThat(
resideInAPackage("..cafe..")
.and(not(resideInAPackage("..cafe.port.."))));
@ArchTest
static final ArchRule common은_어느_도메인모듈도_참조하지_않는다 =
noClasses()
.that().resideInAPackage("..common..")
.should().dependOnClassesThat(
resideInAPackage("..cafe..")
.or(resideInAPackage("..reservation.."))
.or(resideInAPackage("..member..")));
}
실패하는 경우

- was violated (5 times) 뒤에 위반 지점이 하나식 나열된다.
- 클래스명과 줄 번호가 함께 나오므로 어디를 고쳐야 하는지 알 수 있다.
- 규칙 이름도 그래도 출력되므로 여러 규칙 중 어느 것이 깨졌는지 구분된다.
테스트를 만들어두는 것만으로는 부족한 이유
- 테스트 코드를 작성해 두었지만 실행하지 않으면 아무것도 검증되지 않는다.
- 빌드 시 테스트 코드 실행을 강제화 할 필요가 있다.
- Gradle의
build테스트는test를 포함하므로./gradlew build를 실행하면 아키텍처 테스트도 함께 돌고 하나라도 실패하면 빌드가 실패한다. - 여기에 CI를 연결하면 push나 PR마다 자동으로 검증되어 규칙을 어긴 코드는 머지되기 전에 걸린다.
'개발진행목록 > 스터디카페 통합 조회 플랫폼' 카테고리의 다른 글
| [studyhub] Redis 토큰 블랙리스트 구현 (1) | 2026.09.19 |
|---|---|
| [studyhub] 중복 예약 방지와 좌석 이동 기능 구현 (0) | 2026.09.13 |
| [studyhub] 모듈러 모놀리식에서의 인터페이스 정의 (0) | 2026.08.30 |
| [studyhub] 시간대 예약 기능 구현 (0) | 2026.08.27 |
| [studyhub] 카페 등록 구현 (0) | 2026.08.18 |