Files
llm-wiki/wiki/concepts/archunit-scope-classpath-vs-package-filter.md
T

10 KiB

title, source_type, status, confidence, tags, related_projects, last_reviewed
title source_type status confidence tags related_projects last_reviewed
ArchUnit 분석 scope — import scope(어떤 클래스가 검사되는가) vs classpath 의존성(어떻게 검사하는가) llm-generated draft medium
archunit
clean-architecture
testing
static-analysis
jvm
ca-skeleton
2026-06-04

ArchUnit 분석 scope — import scope(어떤 클래스가 검사되는가) vs classpath 의존성(어떻게 검사하는가)

Layer: wiki/concepts/ — 일반 개념. 내 프로젝트 사실(wiki/projects/)은 wiki-project-template 사용.

Summary

ArchUnit rule의 결과는 두 개의 독립적인 축에 의해 결정된다. (1) import scopeClassFileImporter/@AnalyzeClasses가 어떤 class를 분석 대상 집합(JavaClasses)으로 끌어왔는가. (2) classpath 의존성 — 그 class를 분석할 때 ArchUnit이 JVM classpath(reflection)에 의존하는가, 아니면 bytecode만 읽는가. 첫 번째 축을 잘못 잡으면 검사하려던 class가 아예 집합에 없어 rule이 vacuous하게 통과한다(false-negative). 두 번째 축은 대부분의 default rule에서 무관하지만 strongly-typed annotation 접근 같은 일부 ergonomics에만 영향을 준다.

Standard (공식 정의)

  • Import 진입점: class import의 표준 진입점은 new ClassFileImporter().importPackages("<base-package>")이며, JUnit 통합에서는 @AnalyzeClasses(packages = ...)가 같은 역할을 한다. importPackages(...)는 varargs라 다중 package를 받을 수 있고, "단일 root package만 가능"하다는 의미가 아니다. 출처: raw/official-docs/archunit-user-guide (ARCHUNIT-UG-C2).
  • import은 classpath와 무관: ArchUnit은 classpath/JAR/folder 어디서 import했는지와 무관하게 JavaClasses를 구성할 수 있다. 즉 import scope는 "어떤 .class 파일을 읽었는가"의 문제이지 "그 class가 현재 test의 classpath에 있는가"와 자동으로 같지 않다. 출처: raw/official-docs/archunit-conditional-on-property-3-layer-pattern (AUCP-C4).
  • rule 평가는 classpath에 의존하지 않음: ArchUnit 자체의 rule API와 default rule + syntax 조합 평가는 classpath에 의존하지 않는다. 출처: raw/official-docs/archunit-conditional-on-property-3-layer-pattern (AUCP-C4).
  • classpath가 영향을 주는 곳: classpath가 있으면 annotation을 javaClass.getAnnotationOfType(CustomAnnotation.class).value()처럼 strongly-typed로 접근할 수 있고, 없으면 JavaAnnotation<?> + Object value = annotation.get("value") 같은 untyped 접근을 써야 한다. 출처: raw/official-docs/archunit-conditional-on-property-3-layer-pattern (AUCP-C2, AUCP-C3).
  • rule 평가 흐름: rule은 ArchRule 객체로 표현되고 myRule.check(importedClasses) 또는 @ArchTest로 평가된다. @ArchTest가 붙은 rule은 지정된 class를 자동 import(또는 재사용)해 평가한다. 출처: raw/official-docs/archunit-user-guide (ARCHUNIT-UG-C3, ARCHUNIT-UG-C6).

한계 / 주의점

  • package filter가 import scope를 보장하지 않는다: rule의 that().resideInAPackage("..application..")이미 import된 집합 안에서 필터링할 뿐이다. 해당 package의 class가 import scope(@AnalyzeClasses(packages=...) 또는 test classpath)에 애초에 없으면, 위반 코드가 존재해도 매칭 대상이 0개가 되어 rule이 통과한다. 즉 "package glob을 썼으니 그 package를 다 본다"는 착각이 가장 흔한 실패 모드다.
  • 두 가지 빈-집합 동작이 다르다: (a) that() 결과가 비면 ArchUnit은 기본적으로 failed to check any classes 에러를 낸다 — 이때는 눈에 보이는 실패다. 빈 anchor module이 의도된 상태라면 allowEmptyShould(true)로 명시적으로 허용해야 한다. (b) 그러나 검사 대상 class가 import scope 자체에 빠져 있으면 ArchUnit은 그것을 "정상 평가했고 위반 0건"으로 인식해 failed to check any classes 에러조차 내지 않고 BUILD SUCCESSFUL로 통과한다 — 이 vacuous pass가 더 위험하다(에러 신호가 없으므로).
  • allowEmptyShould(true)는 양날의 검: 빈 anchor를 합법화하지만, 동시에 import scope 누락으로 인한 vacuous pass도 똑같이 통과시켜 버린다. 따라서 빈-집합 허용 정책만으로는 rule이 실제로 위반을 잡는지 보증할 수 없다.
  • 권장 보완: (1) 검사 대상이 될 수 있는 module/package(예: sample·fixture)를 test의 import scope에 명시적으로 포함시킨다(예: Gradle testImplementation project(':<sample>')). (2) "위반을 데이터로 보는(violations-as-data)" negative fixture를 두고, 의도된 위반 class에 대해 rule.evaluate(fixtureClasses).hasViolation() == true를 별도 test로 assert해 rule이 진짜 catch하는지 commit으로 보증한다.
  • classpath 의존성과 import scope를 혼동하지 말 것: "classpath에 없어서 못 잡았다"와 "import scope에 안 넣어서 못 잡았다"는 다른 문제다. 전자는 주로 annotation ergonomics(typed accessor)에만 영향을 주고, false-negative의 실제 원인은 거의 항상 후자(import scope 누락)다. 두 축을 섞어 진단하면 엉뚱한 곳을 고친다. (이 구분의 정밀한 경계는 ArchUnit 버전·import 옵션에 따라 달라질 수 있어 needs-confirmation)

Project Application

이 개념과 관련된 내 프로젝트 사실·검증 등급은 아래 project 문서에서 판정한다(concept 문서는 등급을 직접 매기지 않는다).

  • wiki/projects/ca-tmpl/clean-architecture-package-layout — ca-tmpl의 CleanArchitectureTest@AnalyzeClasses(packages = "dev.caskeleton", importOptions = DoNotIncludeTests.class)로 import scope를 잡고, allowEmptyShould(true)로 빈 anchor를 허용하며, ArchitectureViolationFixtureTest(violations-as-data)로 각 rule의 catch 동작을 보증하는 실제 적용.
  • wiki/concepts/clean-architecture-package-layout — 경계 강제(enforcement)의 두 축(build-graph 검사 vs source/bytecode import 검사) 일반 지식.

Claim-backed Knowledge

이 개념 문서의 핵심 설명은 raw source claim으로 뒷받침되어야 한다. 공식 문서 claim과 내 프로젝트 트러블슈팅 사실을 분리한다.

Knowledge Point Supporting Claims Confidence Notes
import 진입점은 ClassFileImporter().importPackages(...)이며 varargs로 다중 package 가능(단일 root 강제 아님) raw/official-docs/archunit-user-guide.md#ARCHUNIT-UG-C2 high 공식 vendor doc
ArchUnit rule API/default rule 평가는 classpath(reflection)에 의존하지 않으며, classpath/JAR/folder 어디서 import했는지와 무관 raw/official-docs/archunit-conditional-on-property-3-layer-pattern.md#AUCP-C4 high 공식 vendor doc. "import scope ≠ classpath presence"의 근거
annotation 접근 ergonomics만 classpath에 의존(있으면 typed .value(), 없으면 untyped JavaAnnotation.get("value")) raw/official-docs/archunit-conditional-on-property-3-layer-pattern.md#AUCP-C2, #AUCP-C3 high classpath가 영향을 주는 유일한 좁은 지점
rule은 ArchRule.check(classes) / @ArchTest로 평가되고, @ArchTest는 지정 class를 자동 import해 평가 raw/official-docs/archunit-user-guide.md#ARCHUNIT-UG-C3, #ARCHUNIT-UG-C6 high 공식 vendor doc
that() 매칭 결과가 비면 기본적으로 failed to check any classes 실패 — 빈 anchor가 의도면 allowEmptyShould(true) 필요 raw/errors/archunit-empty-should-anchor-2026-05-27.md medium 프로젝트 트러블슈팅 사실(error-note). 빈 should 동작
검사 대상 class가 import scope에 빠지면 위반이 있어도 vacuous pass(BUILD SUCCESSFUL, 에러 신호 없음) — sample/fixture를 test import scope에 포함 + negative fixture로 보완 raw/errors/archunit-test-scope-sample-ticket-inclusion-2026-05-28.md medium 프로젝트 트러블슈팅 사실(error-note). 빈 that / import-scope 누락 동작

내가 설명할 수 있어야 하는 것

  • ArchUnit의 import scope와 classpath 의존성은 각각 무엇을 결정하는가?
  • package glob(..application..)을 썼는데도 위반을 놓치는 경우는 왜 생기는가?
  • failed to check any classes 에러가 나는 경우와 나지 않고 통과해 버리는 경우의 차이는 무엇인가?
  • allowEmptyShould(true)는 무엇을 허용하고, 무엇을 막는가?
  • vacuous pass를 어떻게 commit 수준에서 막는가(violations-as-data)?

Interview Questions

  • ArchUnit rule이 통과했는데도 실제로는 boundary가 깨져 있을 수 있는 시나리오는? 어떻게 방지하는가?
  • ArchUnit의 분석이 JVM classpath에 의존하는 부분과 의존하지 않는 부분은 각각 무엇인가?
  • 빈 anchor package가 많은 skeleton에서 architecture test를 신뢰 가능하게 유지하려면 무엇이 필요한가?

Do Not Overclaim

  • "package glob을 쓰면 그 package의 모든 class를 검사한다"는 단정 금지. 검사 대상은 import scope ∩ glob이며, scope에 없으면 검사되지 않는다.
  • "ArchUnit은 classpath가 필요하다/필요 없다"는 단정 금지. default rule 평가는 classpath 독립이지만 typed annotation 접근 같은 ergonomics는 classpath에 의존한다 — 부분적이다.
  • "allowEmptyShould(true)를 켜면 안전하다"는 단정 금지. 빈 should를 허용할 뿐, import scope 누락으로 인한 vacuous pass는 막지 못한다.
  • 위 빈-집합/scope 동작의 정밀한 경계는 ArchUnit 버전·import 옵션에 따라 달라질 수 있어 일부는 needs-confirmation이다.

Sources