--- title: error / archunit-test-scope-sample-ticket-inclusion-2026-05-28 source_type: error-note status: raw related_branches: [feature-application-port-usecase-contract] related_projects: [ca-skeleton] tags: [error, ca-skeleton, archunit, test-scope, gradle, sample-ticket] created: 2026-05-28 status_label: resolved --- # error: archunit-test-scope-sample-ticket-inclusion-2026-05-28 > Layer: `raw/errors/` — 작업 중 마주친 단일 실패·트러블슈팅 기록. 원본은 raw 에 영구 보관. ## Parent / 부모 - [[raw/branch-notes/feature-application-port-usecase-contract]] — `application_does_not_use_spring_transactional_annotation` ArchUnit rule 추가 중 vacuously 통과한 함정 + `testImplementation project(':sample-ticket')` 으로 해결한 경험. - [[raw/project-notes/ca-skeleton-operational-contract]] — ca-tmpl skeleton 의 boundary 자동 검증 정책 맥락. ## 증상 / Symptom - 에러 메시지 (원문 그대로): ```text BUILD SUCCESSFUL in 5s 16 actionable tasks: 3 executed, 13 up-to-date ``` _기대값_ 은 `application_does_not_use_spring_transactional_annotation` rule 의 _실패_ (당시 `sample-ticket/.../UserService` 와 `PostService` 가 `org.springframework.transaction.annotation.Transactional` 을 import 중). 그러나 BUILD SUCCESSFUL — rule 이 _vacuously_ 통과. ArchUnit 의 "failed to check any classes" 에러조차 _뜨지 않음_ (rule 이 정상 평가됐다고 인식). - 발생 컨텍스트: `cd src && ./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest'`. 새 ArchUnit rule 3종 (`application_does_not_use_spring_transactional_annotation`, `inbound_port_implementations_end_with_use_case`, `inbound_port_implementations_declare_capability`) 추가 직후 첫 실행. - 발생 시점: 2026-05-28. - 발생 환경: ca-tmpl repository, local Linux. - 재현 가능 여부: `always` — `app-bootstrap` 의 production dependency 매트릭스에 `sample-ticket` 이 없는 상태에서 ArchUnit rule 이 `..application..` 패키지를 검사하면 재현. ## 재현 절차 / Reproduction 1. ca-tmpl `src/build.gradle` 의 `allowedProjectDependencies` 매트릭스에서 `app-bootstrap` 의 allowed 목록에 `sample-ticket` 이 _없음_ 을 확인. 2. `src/sample-ticket/.../application/UserService.java` 에 `import org.springframework.transaction.annotation.Transactional;` 가 _있음_ 을 확인. 3. `src/app-bootstrap/.../CleanArchitectureTest.java` 에 다음 rule 을 추가: ```java @ArchTest static final ArchRule application_does_not_use_spring_transactional_annotation = noClasses() .that().resideInAPackage("..application..") .should().dependOnClassesThat().haveFullyQualifiedName( "org.springframework.transaction.annotation.Transactional" ) .allowEmptyShould(true); ``` 4. `cd src && ./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest'` 실행. 5. 기대 결과: `application_does_not_use_spring_transactional_annotation` rule 이 _실패_ (UserService / PostService 가 위반). 6. 실제 결과: BUILD SUCCESSFUL. rule 이 _vacuously_ 통과. ## 조사 단계 / Investigation log - 2026-05-28 — ArchUnit test 실행 → 모든 rule 통과 → 의외. `sample-ticket` 의 `@Transactional` 이 분명히 남아 있는데? - 2026-05-28 — `CleanArchitectureTest.java` 의 `@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ImportOption.DoNotIncludeTests.class)` 확인 → 패키지 필터는 `dev.caskeleton` 이지만, _실제 import 대상은_ `app-bootstrap` 의 test classpath 에 _존재_ 하는 클래스 중 패키지 필터 매치 부분이다. - 2026-05-28 — `src/build.gradle` 의 `allowedProjectDependencies` 매트릭스 확인 → `app-bootstrap` 의 allowed = `['domain-core', 'application-core', 'adapter-web', 'adapter-persistence', 'adapter-outbound', 'shared-contract']` — `sample-ticket` 은 _의도적으로 부재_ (production 역수입 금지의 자매 결정). - 2026-05-28 — 결론: `sample-ticket` 의 main 클래스는 `app-bootstrap` 의 _test JVM classpath_ 에 _없음_. ArchUnit 가 패키지 필터 `dev.caskeleton` 으로 import 해도 `sample-ticket` 의 클래스를 못 봄. 그래서 rule 이 _0 개의 application 클래스_ 를 평가했고, `allowEmptyShould(true)` 가 _true_ 로 해석. - 2026-05-28 — 해결 후보 검토: - (a) `app-bootstrap` 에 `implementation project(':sample-ticket')` 추가 → production dependency 매트릭스 위반, `verifyCleanArchitectureDependencies` 실패. 채택 안 함. - (b) `app-bootstrap` 에 `testImplementation project(':sample-ticket')` 추가 → production 매트릭스 영향 없음 (`['api', 'implementation', 'compileOnly', 'runtimeOnly']` 만 검사). ArchUnit `production_code_does_not_depend_on_sample_ticket` 도 `ImportOption.DoNotIncludeTests` 로 _test 클래스 제외_ 하므로 production drift 로 잘못 보고 안 됨. **채택**. - 2026-05-28 — `app-bootstrap/build.gradle` 에 `testImplementation project(':sample-ticket')` 추가 후 재실행 → 이제는 `sample-ticket` 클래스가 ArchUnit scope 에 잡혀서 `application_does_not_use_spring_transactional_annotation` rule 이 _실패_ (예상대로). - 2026-05-28 — `sample-ticket` 의 `UserService` / `PostService` 의 `@Transactional` 을 모두 `TransactionPort` 호출로 치환 → 재실행 → BUILD SUCCESSFUL. red/green 검증 완료. ## 근본 원인 / Root cause - 직접 원인: `app-bootstrap` 의 production dependency 매트릭스에 `sample-ticket` 이 없어서 `sample-ticket` 의 main 클래스가 `app-bootstrap` 의 test JVM classpath 에 없었음. ArchUnit 의 `@AnalyzeClasses(packages = "dev.caskeleton")` 는 패키지 _필터_ 일 뿐 _classpath scan source_ 가 아님. - 근본 원인: ArchUnit 의 import scope 가 _현재 모듈의 컴파일 + 런타임 classpath_ 에 의존한다는 점을 _패키지 필터_ 만 보면 놓치기 쉬움. 패키지 필터가 "이 패키지를 검사한다" 의 _전제_ 가 아니라 _필터_ 임을 인식 못 함. - 트리거 조건: `sample-ticket` 이 production 역수입 금지 정책에 따라 `app-bootstrap` 의 production dep 가 _아님_ (의도된 정책) + ArchUnit rule 이 `..application..` 패키지를 검사 (sample-ticket 도 이 패키지에 포함됨) 의 _교차_ 상황. ## Sources / 근거 - [[raw/branch-notes/feature-application-port-usecase-contract]] — 본 에러를 발견한 작업의 branch-note + Decisions 2026-05-28. - [[raw/branch-notes/feature-architecture-enforcement-rules]] — `production_code_does_not_depend_on_sample_ticket` rule 의 `ImportOption.DoNotIncludeTests` 사용 (D7). - [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] — `sample-ticket` 의 production 역수입 금지 결정 (D7). - [[raw/errors/archunit-empty-should-anchor-2026-05-27]] — ArchUnit 의 _빈 평가_ 와 `allowEmptyShould(true)` 의 또 다른 함정 사례. - ca-tmpl 코드: `src/build.gradle` `verifyCleanArchitectureDependencies` task — `['api', 'implementation', 'compileOnly', 'runtimeOnly']` 만 검사. `testImplementation` 은 production 매트릭스에서 _제외_. - ca-tmpl 코드: `src/app-bootstrap/.../CleanArchitectureTest.java` `@AnalyzeClasses(importOptions = ImportOption.DoNotIncludeTests.class)`. ## 해결 / Resolution - 적용한 조치: `src/app-bootstrap/build.gradle` 의 `dependencies` 블록에 `testImplementation project(':sample-ticket')` 추가. 주석으로 비대칭 의존의 의도를 명시: ```gradle // sample-ticket is on the test classpath only so the ArchUnit suite can analyse the // template's reference implementation. Production scope MUST NOT depend on // sample-ticket; that rule is enforced by `production_code_does_not_depend_on_sample_ticket`. testImplementation project(':sample-ticket') ``` - 검증 방법: - `cd src && ./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest'` — `application_does_not_use_spring_transactional_annotation` 가 _실패_ 시키는지 확인 (`sample-ticket` migration 전). - sample-ticket migration 후 동일 명령 실행 → BUILD SUCCESSFUL. red/green 양쪽 확인. - `cd src && ./gradlew verifyCleanArchitectureDependencies` — production 매트릭스 영향 없음 확인. - `cd src && ./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest'` 의 `production_code_does_not_depend_on_sample_ticket` rule — 여전히 통과 (test 클래스 제외 옵션 때문). - 잔여 위험 / 후속 작업: - 다른 sample / fixture 모듈이 추가될 경우 동일 패턴 (`testImplementation project(':')`) 을 명시적으로 적용해야 함. 누락되면 vacuously pass 재발 가능. - ArchUnit rule 을 추가할 때 _수반_ 해야 할 체크리스트 (해당 rule 이 잡으려는 _violating example_ 이 test classpath 에 있는지) 가 명시화되지 않음 — 후속 review checklist 후보. ## 회고 / Lessons - 빨리 감지하는 신호: - ArchUnit rule 을 추가했는데 _실패할 거라고 100% 확신_ 한 케이스가 통과하면 _rule 이 잘못된 게 아니라 import scope 가 비어 있을_ 가능성을 첫 의심. - `BUILD SUCCESSFUL` + 새 rule 의 `failed to check any classes` 경고조차 _없음_ → 패키지 필터에 매치되는 클래스가 _classpath 에 없는_ 상태. - 새 rule 을 PR 에 넣기 전 _임시 violating code_ 를 추가해 _red 가 되는지_ 확인 (`feature-architecture-enforcement-rules.md` 의 red/green 패턴과 동일). - 예방 체크리스트 항목 후보: - 새 ArchUnit rule 추가 시 _이 rule 이 잡으려는 위반 예시가 ArchUnit 의 import scope (= 현재 모듈의 test JVM classpath) 에 실제로 존재하는가_ 를 먼저 확인. - 새 sample / fixture 모듈 추가 시 `app-bootstrap/build.gradle` 의 `testImplementation` 에 명시 추가 + 주석으로 비대칭 의존 이유 기록. - ArchUnit `@AnalyzeClasses(packages = ...)` 가 _필터_ 일 뿐 _scan source_ 가 아니라는 사실을 PR 리뷰 checklist 에 추가. - wiki 로 끌어올릴 가치가 있는 일반화된 교훈: - ArchUnit 의 _scope = classpath ∩ package filter_. 둘 중 하나가 비어 있으면 vacuously pass. - production dependency 차단 (`production_code_does_not_depend_on_sample_ticket`) 과 test-scope inclusion (`testImplementation project(':sample-ticket')`) 의 _비대칭 의존_ 패턴은 sample / fixture module 이 있는 multi-module repo 에 일반적으로 적용 가능. ## Related / 관련 - 트리거된 daily note: [[raw/daily-notes/2026-05-28]]. - 관련 branch note: [[raw/branch-notes/feature-application-port-usecase-contract]], [[raw/branch-notes/feature-architecture-enforcement-rules]] (자매 — boundary 자동 검증). - 관련 errors: [[raw/errors/archunit-empty-should-anchor-2026-05-27]] (ArchUnit empty pass 의 또 다른 변종). - 관련 wiki 개념: [[wiki/concepts/clean-architecture-package-layout]] (아직 갱신 전), 후보 [[wiki/concepts/archunit-scope-classpath-vs-package-filter]] (정제 시 신규). - 관련 blog topics: [[raw/blog-topics/transaction-port-abstraction-over-spring-transactional-2026-05-28]] (본 에러를 발견한 작업의 글감), [[raw/blog-topics/clean-architecture-boundary-enforcement-2026-05-28]] (boundary 자동 검증 글감).