52 KiB
title, source_type, status, branch, parent_branch, related_projects, governing_docs, tags, created, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
| title | source_type | status | branch | parent_branch | related_projects | governing_docs | tags | created | target_merge | status_label | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | contract_packet_sha256 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-test-taxonomy-fixture-contract | branch-note | raw | feature-test-taxonomy-fixture-contract |
|
|
|
2026-05-22 | review | BR-CA-SKELETON-OPERATIONAL-CONTRACT-042 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-042 |
|
1 | df97c653e6bc2a4b42673993d1881ecd4a683c40c133c115f9f041fef42add59 |
branch: feature-test-taxonomy-fixture-contract
Layer:
raw/branch-notes/— unit/contract/architecture/slice/integration/smoke 테스트의 책임과 fixture 사용 기준을 정의합니다.
부모 (필수)
- Parent project (canonical SSOT): raw/project-notes/ca-skeleton-operational-contract
ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§29 G-G Test taxonomy · §17/§22 Sample fixture) 의 결정/근거/금지 사항을 정제한다.
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: test level별 fixture가 실행되고 container 사용 정책을 지킨다
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-TESTCONTAINERS-001@1 |
Testcontainers는 persistence/outbound integration test에 사용하고 unit·architecture·contract test에서는 금지한다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-TEST-001@1 |
test framework는 JUnit 5다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
브랜치 지역 결정
기존 branch-local 결정은 아래
## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
목표
테스트가 많아도 실패 원인을 구분할 수 없으면 실무 skeleton으로 부족합니다. 이 branch는 어떤 계약을 어떤 테스트 레벨에서 잡을지 고정하고, sample-portfolio과 fixture가 테스트를 오염시키지 않게 합니다.
- 이슈:
- PR:
범위
포함 범위
- unit test 기준.
- contract test 기준.
- architecture test 기준.
- slice test 기준.
- integration test 기준.
- smoke test 기준.
- fixture/test data policy.
제외 범위
- load/performance test.
- chaos engineering.
- external provider E2E test.
근거 (필수, 최소 1개+)
본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.
| Source | 정당화하는 결정 |
|---|---|
| raw/official-docs/test-taxonomy-testcontainers-official | Testcontainers 공식 "real services, no H2" 입장과 정합 |
| raw/official-docs/test-taxonomy-practical-pyramid-fowler | Fowler/Cohn pyramid; ca-tmpl 6-level이 contract·architecture·slice를 명시 분리 |
| raw/company-tech-blogs/test-pyramid-vs-trophy-kent-dodds | static/integration heavy; React 진영 영향 |
| raw/official-docs/spring-boot-test-slices-webmvctest-datajpatest-official | D7 — Spring slice(@WebMvcTest/@DataJpaTest) semantics 및 "여러 slice annotation 혼용 미지원" 공식 정의 (SB-SLICE-C1, SB-SLICE-C2, SB-SLICE-C3, SB-SLICE-C4) |
| raw/official-docs/governance-archunit-official | D2 — ArchUnit이 "Java 코드 architecture(package/class dependency, layer/slice, cyclic)를 plain unit test framework로 검사" 공식 정의 (AU-OFF-C1, AU-OFF-C2) |
| raw/official-docs/archunit-user-guide | D2 — package 의존 규칙 fluent DSL (ARCHUNIT-UG-C4) |
| raw/official-docs/archunit-conditional-on-property-3-layer-pattern | D2 — Building Evolutionary Architectures fitness function 정의 = "아키텍처 특성에 대한 객관적 무결성 평가 mechanism" (AUCP-C5) |
외부 근거 / 대안 조사 (2026-05-22 — Group G-G: Test Taxonomy / Fixture)
본 branch의 6 levels (unit/contract/architecture/slice/integration/smoke) + Testcontainers from integration + src/testFixtures + 5min budget 결정에 대한 외부 source.
- 채택 결정 (6-level taxonomy + Testcontainers integration only):
- raw/official-docs/test-taxonomy-testcontainers-official — Testcontainers 공식 "real services, no H2" 입장과 정합
- 검토한 대안:
- 대안 1: Classic test pyramid (unit/integration/e2e) — raw/official-docs/test-taxonomy-practical-pyramid-fowler (Fowler/Cohn pyramid; ca-tmpl 6-level이 contract·architecture·slice를 명시 분리)
- 대안 2: Test trophy (Kent Dodds) — raw/company-tech-blogs/test-pyramid-vs-trophy-kent-dodds (static/integration heavy; React 진영 영향)
- 대안 3: Honeycomb (Spotify) — slim unit, fat integration
- 대안 4: Fitness functions (evolutionary architecture) — Building Evolutionary Architectures, ca-tmpl architecture-test가 일부 해당
- 비교 핵심: ca-tmpl 6-level taxonomy는 classic pyramid에 contract·architecture·slice를 명시 분리한 형태. 5min budget + Testcontainers cost가 unit/contract/architecture를 integration과 분리한 핵심 이유. Testcontainers 공식 "real services" 입장이 ca-tmpl integration-only 강제와 정합. Test trophy/Honeycomb은 frontend·SPA 진영이라 backend ca-tmpl과 trade-off 다름.
추가 조사 (2026-06-15 — /branch-spec 자동조사: D2·D7 UNSUPPORTED 해소)
- D2 (architecture test as level) — ArchUnit 공식 + Building Evolutionary Architectures:
- 비교한 대안: (1) ArchUnit 전용 architecture-test level, (2) fitness function 일반 메커니즘(jQAssistant/Deptective/custom), (3) 수동 코드 리뷰.
- 조건부 결론: ca-tmpl 처럼 패키지 경계 = layer 경계인 JVM/Spring Boot 프로젝트 → Alt 1(ArchUnit). 이미
archunit-junit5:1.3.0의존성 존재(도입비용 0). 복잡한 경계(그래프 탐색 필요) → Alt 2(jQAssistant, 단 GPLv3). 1~2인 단명 프로젝트 → Alt 3(수동, 단 skeleton fork 강제력 없음 → ca-tmpl 부적합). - 잔존 갭: "architecture-test를 unit/integration과 동급의 별도 taxonomy level로 정의한 업계 공식 표준은 없음." fitness function 개념이 "architecture test ≠ unit test"임을 book-grade authority로 간접 지지하는 수준. Open Risk(D2)에 명시.
- D7 (Spring slice test) — Spring Boot 공식 reference:
- 비교한 대안: (1) Spring test slice(
@WebMvcTest/@DataJpaTest), (2)@SpringBootTest전체 context, (3)MockMvcBuilders.standaloneSetup/순수 mock. - 조건부 결론: controller HTTP wire(routing/advice/security) →
@WebMvcTest(slice level). JPA query →@DataJpaTest(slice level). 전체 context wire →@SpringBootTest(= integration level). Spring 없는 controller 단위 →standaloneSetup(= unit level). 두 slice annotation 한 클래스 혼용은 Spring 공식이 "not supported"(SB-SLICE-C2) → forbidden 직접 근거. - 잔존 갭: "hex use-case slice 와 Spring slice 명시 분리"의 hex 측 외부 근거는 미archive —
UNSUPPORTED_IMPL_DECISION잔존(D7 Open Risk).
- 비교한 대안: (1) Spring test slice(
TODO
TODO drained 2026-05-22 — 결정은 아래 "결정 사항" / "Test Level Matrix" / "판정 기준" / "테스트 계약" 참조. taxonomy 구분/fixture 사용/test data PII/optional adapter matrix/failure ownership/CI gate mapping 모두 표 또는 결정 라인으로 반영됨. 잔존 TODO 없음.
Work Item Contract
각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 기준 작성으로 남아 있으면 이 branch는 완료로 보지 않습니다.
| field | required | rule |
|---|---|---|
| Decision | yes | 구현자가 선택해야 하는 기본값 |
| Allowed | yes | 허용되는 예외와 조건 |
| Forbidden | yes | 절대 금지되는 구현/문서 상태 |
| Required registry update | conditional | error/env/header/log/metric/capability 변경 시 필수 |
| Required contract test | yes | 계약 위반 시 실패해야 하는 테스트 |
| Failure condition | yes | review/build에서 실패로 판정할 상태 |
| Canonical extraction target | yes | wiki/projects 승급 위치 |
진행 중 메모
문서/설계 단계. 코드 구현은 ca-tmpl 측에서 진행 중이며, 본 노트의 일부 결정은 실제 구현과 drift 발생 — §Audit & Findings 참조.
- 2026-06-15 (
/branch-spec): ca-tmpl ground truth 대조 결과, 본 branch 결정 중 architecture-test(D2)·contract-test(D1/D5)·slice(D7)·Testcontainers(D3)·sample 누수 방어(테스트 계약)는 이미 코드에 구현되어 있음(actually-implemented). 단 fixture 배치(D6)·contract 도구(D5)·sample 누수 방어 메커니즘은 결정과 코드가 불일치(§Audit). 노트의 "현재 documented-only 단계" 자기 서술은 stale.
결정 사항
- 2026-05-22: contract test는 운영 계약 위반을 잡고, integration test는 provider 연동을 잡음.
- 2026-05-22: architecture test는 CA boundary와 package blueprint 위반을 잡음.
- 2026-05-22: Testcontainers는 integration test부터 강제. unit/contract/architecture test는 Testcontainers 금지.
- 2026-05-22: CI time budget 기본값은 unit+contract+architecture 5분 이내, integration matrix는 별도 gate.
- 2026-05-22: contract test 도구 = (1) envelope/error/log/env shape: JSON snapshot test (
approvaltests-java또는 자체 snapshot) (2) OpenAPI drift: springdoc-openapi 생성 vs checked-in snapshot diff (3) consumer-driven contract는 boundary 외부 통합 시만 도입(현재 skeleton out-of-scope). - 2026-05-22: fixture 위치 = Gradle
src/testFixtures/java/<feature>/source set. 명명 =*Fixture.java(정적 factory),*Mother.java는 alias. - 2026-05-22: slice test 정의 = Spring slice (
@WebMvcTest/@DataJpaTest)는 허용 단 hex slice(use case + port + mapper)와 명시적으로 분리. 동일 메서드에 두 slice annotation 혼용은 forbidden. - 2026-05-22: flaky test ownership = test file의 첫 author 또는 가장 최근 maintainer. 14일 quarantine sunset (
feature-ci-quality-gates와 cross-link). - 2026-05-22: flaky test quarantine 정책 SSOT는 ci-quality-gates-contract(sunset 14일). 본 branch는 flaky 발생 시 quarantine bucket 분리만 명시.
- 2026-06-19 (구현 정합 — ca-tmpl 코드 작업, 사용자 fork 확정): 노트가 "사용자 정합" 으로 남겨둔 4개 fork 를 확정하고
app-bootstraptest 트리에 구현.- (1) D3 / DIR_LEVEL: Testcontainers 를 쓰던
bootstrap/contract[/outbox]/5개 test +OutboxContainerTestSupport를bootstrap/integration[/outbox]/로 재분류 +..contract..·..architecture..패키지가 Testcontainers 에 의존하면 fail 하는 ArchUnit ruleTestTaxonomyArchitectureTest.contract_and_architecture_tests_do_not_depend_on_testcontainers(positive-control 로..integration..에서 발화 증명) 추가 → §테스트 계약 #4 ENFORCED. - (2) D6 / FIXTURE_LAYOUT:
src/testFixtures마이그레이션 대신 현행fixtures/package 유지 + main classpath 누수 차단 ruleCleanArchitectureTest.production_code_does_not_depend_on_test_fixtures(@ArchTest, fixtureleak violation 으로 meta-verify) 추가. 2026-05-22 의src/testFixtures+*Mother결정은 superseded — 현 fixture 는 모듈 간 공유가 아니라 source-set 분리 이점이 낮음. - (3) SAMPLE_GUARD: sample 누수 방어는 기존 build-time ArchUnit module rule
production_code_does_not_depend_on_sample_portfolio유지 — runtime@ActiveProfiles(prod/staging)ApplicationContext check 는 미채택, §테스트 계약 #3 명세를 build-time 기준으로 갱신(prod/staging yml 신설 없음). - (4) D4 / CI: 5분 budget + contract-change/blueprint-change 동반 git-diff gate 는 GitHub Actions 신설 보류(
planned) — ca-tmpl 에 CI workflow 부재, CI matrix 는 raw/branch-notes/feature-ci-quality-gates-contract 와 함께 후속. - 추가: D7 slice-mixing ban(SB-SLICE-C2) 을
TestTaxonomyArchitectureTest.slice_tests_do_not_mix_two_spring_slice_annotations(@WebMvcTest+@DataJpaTest한 클래스 금지; over-block guard 포함) 으로 구현. hex-slice 분리는 convention 유지(UNSUPPORTED_IMPL_DECISION). - 검증:
:app-bootstrap:test --tests '*TestTaxonomyArchitectureTest'6/6 PASS,--tests '*CleanArchitectureTest'PASS,verifyCleanArchitectureDependenciesGREEN, architect-sentinel ready(0 blocking). 변경은src/test/**한정 — production·src/build.gradle·module 의존 그래프 무변경. 계획서:ca-tmpl/docs/superpowers/plans/2026-06-19-test-taxonomy-fixture-contract.md.
- (1) D3 / DIR_LEVEL: Testcontainers 를 쓰던
판정 기준
| 구분 | 기준 |
|---|---|
| Decision | test taxonomy를 분리해 실패 원인을 즉시 알 수 있게 함 |
| Allowed | 작은 프로젝트는 디렉터리를 합치되 test tag/name으로 구분 |
| Forbidden | contract violation을 integration test에서만 우연히 발견 |
| Required groups | unit, contract, architecture, slice, integration, smoke |
| Failure condition | 어떤 테스트가 어떤 계약을 보호하는지 문서화되지 않으면 실패 |
Test Level Matrix
| level | owns | Testcontainers |
|---|---|---|
| unit | pure function/domain rule | no |
| contract | response/log/env/error/registry contract | no |
| architecture | package/import/capability rules | no |
| slice | controller/use case/mapper slice | optional no external provider |
| integration | DB/Redis/Kafka/outbound provider | yes when provider needed |
| smoke | bootstrap/sample removal/startup | optional |
결정-근거 매핑
각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시.
Decision ID는 이 branch-note 안에서 안정적으로 유지.선택 조건열(R2): "이 조건일 때 이 결정, 다른 조건이면 어떤 대안". 분기 없으면N/A.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | contract test는 운영 계약 위반을 잡고, integration test는 provider 연동을 잡음 | 운영 계약(envelope/error/log/env/registry) shape 위반 검증 → contract test(Testcontainers 없음). 실제 provider 연동(DB/Redis/Kafka/outbound) 검증 → integration test. 계약과 연동을 한 테스트에 섞으면 실패 원인 모호 → 항상 분리 | raw/official-docs/test-taxonomy-practical-pyramid-fowler.md#TPP-FOWLER-C2, raw/official-docs/test-taxonomy-practical-pyramid-fowler.md#TPP-FOWLER-C4, raw/official-docs/test-taxonomy-practical-pyramid-fowler.md#TPP-FOWLER-C6 |
engineering-blog (Fowler/Vocke 정의 + 팀 합의 원칙) |
Fowler 의 narrow integration 정의는 "test double" 가정 — Testcontainers real-container 와의 일관성은 별도 검증 |
| D2 | architecture test는 CA boundary와 package blueprint 위반을 잡음 | 패키지 경계 = layer 경계인 JVM/Spring Boot → ArchUnit architecture-test(이 결정). 경계가 annotation/runtime 기반이거나 polyglot → fitness function 일반 메커니즘(jQAssistant). 1~2인 단명 프로젝트 → 수동 리뷰. ca-tmpl 은 전자 | raw/official-docs/governance-archunit-official.md#AU-OFF-C1, raw/official-docs/governance-archunit-official.md#AU-OFF-C2, raw/official-docs/archunit-user-guide.md#ARCHUNIT-UG-C4, raw/official-docs/archunit-conditional-on-property-3-layer-pattern.md#AUCP-C5 (fitness function 정의) — ca-tmpl 코드 actually-implemented: app-bootstrap/.../architecture/CleanArchitectureTest.java, DisabledAdapterArchitectureTest.java (archunit-junit5:1.3.0, build.gradle:47) |
official-vendor-doc (ArchUnit) + book-concept (Evolutionary Architectures via AUCP-C5) |
"architecture-test를 별도 taxonomy level로 정의한 업계 공식 표준은 없음" — fitness function 개념이 unit test와 다른 관심사임을 간접 지지하는 수준. 단 ca-tmpl 코드엔 실제 구현됨 → §Audit D2_NOW_IMPLEMENTED |
| D3 | Testcontainers는 integration test부터 강제. unit/contract/architecture test는 Testcontainers 금지 | real service(DB/Redis/Kafka/provider) 필요 → integration test에서 Testcontainers. pure logic/계약 shape/패키지 규칙 → Testcontainers 금지(5min budget·D4 보호). H2 대체는 Testcontainers 공식이 부적합 명시 | raw/official-docs/test-taxonomy-testcontainers-official.md#TC-OFFICIAL-C1, raw/official-docs/test-taxonomy-testcontainers-official.md#TC-OFFICIAL-C3, raw/official-docs/test-taxonomy-testcontainers-official.md#TC-OFFICIAL-C5 — ca-tmpl actually-implemented: testcontainers:postgresql+junit-jupiter (app-bootstrap build.gradle:28-29), @Testcontainers in contract/outbox/* |
official-vendor-doc (real services + H2 한계) |
Testcontainers 공식은 integration 권장만, 다른 level 금지는 ca-tmpl 별도 결정 (5분 budget 보호) |
| D4 | CI time budget 기본값은 unit+contract+architecture 5분 이내, integration matrix는 별도 gate | unit+contract+architecture 합산 → 5분 gate. integration matrix → 별도 gate(시간 무제한). 5분은 local fast-feedback 목표치이지 측정된 임계값 아님 | UNSUPPORTED_DECISION (자료에 5분 정량 기준 부재) | team-policy (Testcontainers TC-OFFICIAL-C4 의 "IDE 실행 가능성" 만 간접 지지) |
실제 측정으로 5분 임계점 검증 필요 (container start cost 포함). §Claims To Verify 1행 |
| D5 | contract test 도구 = JSON snapshot (approvaltests-java) + OpenAPI drift (springdoc) + CDC out-of-scope |
envelope/error/log/env shape → JSON snapshot. OpenAPI drift → springdoc 생성 vs checked-in diff. boundary 외부 통합 시만 → CDC(현 skeleton out-of-scope) | raw/official-docs/test-taxonomy-testcontainers-official.md#TC-OFFICIAL-C1 (Testcontainers 자체 정의는 integration 영역) — contract 도구 자체 근거는 feature-contract-verification-test-suite branch 의 source 가 SSOT(위임) |
cross-branch-reference |
본 branch 의 책임 범위 — 도구 선택 근거는 verification branch 가 owner. DRIFT: 실제 코드는 approvaltests-java 미사용, OpenApiSnapshotTest.java 기반 → §Audit CONTRACT_TOOL_DRIFT |
| D6 | fixture 위치 = Gradle src/testFixtures/java/<feature>/ source set, 명명 *Fixture.java / *Mother.java alias |
fixture가 여러 test module에서 재사용 → 공유 source set(이 결정). 단일 모듈 한정 → 해당 모듈 test 트리 내 package | UNSUPPORTED_DECISION (자료에 src/testFixtures 권장 직접 명시 없음) | team-convention (Gradle Java Library plugin 공식 페이지 별도 raw 등록 권고) |
Gradle 공식 documentation raw source 보강 필요. DRIFT: 실제 코드는 java-test-fixtures 플러그인/src/testFixtures 미적용 — fixtures는 src/test/java/.../fixtures/ package(예: sample-portfolio/.../fixtures/SamplePortfolioFixture.java), *Mother.java 없음 → §Audit FIXTURE_LAYOUT_DRIFT (사용자 정합 필요) |
| D7 | slice test 정의 = Spring slice 허용하되 hex slice 와 명시 분리, 동일 메서드 혼용 forbidden | controller HTTP wire(routing/advice/security) → @WebMvcTest. JPA query → @DataJpaTest. 전체 context wire → @SpringBootTest(=integration level). Spring 없는 controller 단위 → standaloneSetup(=unit level). 두 slice annotation 한 클래스 혼용 → forbidden |
raw/official-docs/spring-boot-test-slices-webmvctest-datajpatest-official.md#SB-SLICE-C1 (slice semantics — 제한된 component scan), raw/official-docs/spring-boot-test-slices-webmvctest-datajpatest-official.md#SB-SLICE-C2 (여러 @…Test 혼용 not supported — 혼용 forbidden 직접 근거), raw/official-docs/spring-boot-test-slices-webmvctest-datajpatest-official.md#SB-SLICE-C3 (@WebMvcTest scan 목록), #SB-SLICE-C4 (@Component 자동 제외) — ca-tmpl actually-implemented: @WebMvcTest in sample-portfolio/.../WorkLogControllerWireTest. @DataJpaTest 미사용(planned). "hex slice 와 명시 분리" 부분은 UNSUPPORTED_IMPL_DECISION 잔존 |
official-vendor-doc (혼용 금지) + team-convention (hex 분리) |
"hex slice 와 명시 분리" 결정의 외부 근거 보강 필요 (hexagonal architecture 원전 raw 미등록) |
| D8 | flaky test ownership = test file 첫 author 또는 가장 최근 maintainer, 14일 quarantine sunset | flaky 발생 → 본 branch 는 quarantine bucket 분리만. ownership/sunset 정책 자체 → feature-ci-quality-gates-contract 가 SSOT(위임) |
UNSUPPORTED_DECISION (본 branch 자체에 ownership/sunset 자료 인용 없음 — feature-ci-quality-gates-contract 의 company-case-study (Spotify/Google quarantine) 가 SSOT) |
cross-branch-reference |
ci-quality-gates-contract 의 company-case-study 는 official best practice 아님 |
구현 가이드
결정이 "무엇을 할 것인가"라면, 본 §는 "어디에 어떻게 구현되는가"의 사전 명세 — 다음 구현자가 되묻지 않아도 코드를 작성할 수 있는 수준. anchor 는 §2(
/branch-spec)에서 대조한 실제 ca-tmpl 코드 경로이며, 코드로 확인 안 된 것은planned로 표기. 3-rule: R1 각 cell 은 Decision ID + Supporting Claim 도출 · R2 근거 없는 detail 은UNSUPPORTED_IMPL_DECISION+ trade-off 한 줄 · R3 본 branch 범위 밖은 §Audit 로 이관.
1. Test level → 디렉터리/메커니즘 매핑
Trace: D1 · D2 · D3 · D7 + Test Level Matrix. 등급은 §2 코드 grep 으로 확정.
| level | 실제 경로/메커니즘 (ca-tmpl) | 등급 |
|---|---|---|
| unit | domain-core/src/test/java/.../unit/ + 도메인 per-class 테스트 (Testcontainers 없음) |
actually-implemented (부분) |
| contract | <module>/src/test/java/.../contract/ — app-bootstrap(18 classes)·adapter-outbound·adapter-web·shared-contract. D1 운영 계약 shape 검증 |
actually-implemented |
| architecture | app-bootstrap/.../architecture/ — CleanArchitectureTest.java(domain_is_pure, value_objects_have_no_public_no_arg_constructor, production_code_does_not_depend_on_sample_portfolio:576), DisabledAdapterArchitectureTest.java. archunit-junit5:1.3.0. D2 |
actually-implemented |
| slice | @WebMvcTest — sample-portfolio/.../WorkLogControllerWireTest·VersioningPrefixTest. @DataJpaTest 없음. D7(SB-SLICE-C1/C3/C4) |
actually-implemented(@WebMvcTest) / planned(@DataJpaTest) |
| integration | app-bootstrap @Testcontainers (contract/outbox/Outbox*ContractTest). D3(TC-OFFICIAL-C1) |
actually-implemented |
| smoke | app-bootstrap/.../smoke/ (bootstrap/startup). D1 |
actually-implemented(부분) |
- UNSUPPORTED_IMPL_DECISION: 6-level 을 디렉터리에 1:1 강제하는 메커니즘(어떤 test가 어떤 level인지 ArchUnit rule/JUnit tag로 고정)은 미정 — 현재는 디렉터리 convention 만 존재. trade-off: convention 은 가볍지만 신규 test가 잘못된 level 에 놓여도 build 가 막지 않음(강제 < 관례). 강제까지 원하면
@Tag+ ArchUnit "test class 위치 ↔ tag 일치" rule 추가 필요(planned).- integration test 가 ca-tmpl 에서
contract/outbox/하위에 위치 — Test Level Matrix 의 level 명과 디렉터리 명이 1:1 아님(outbox integration 이 contract 폴더 안). 명칭 정합은 §Audit 후보(비차단).
2. Fixture 배치 (D6) — 결정 vs 코드 DRIFT
Trace: D6 (UNSUPPORTED_DECISION). §2 코드 대조에서 drift 확정.
- 결정 명세:
src/testFixtures/java/<feature>/Gradle source set +*Fixture.java/*Mother.java. - 실제 코드(
actually-implemented): fixtures 는 test source set 내fixtures/package (sample-portfolio/.../fixtures/SamplePortfolioFixture.java) + ArchUnit violation fixtures (architecture/violations/.../*Fixture.java).java-test-fixtures플러그인·src/testFixtures디렉터리 없음.*Mother.java없음. - UNSUPPORTED_IMPL_DECISION + DRIFT: 결정과 코드가 불일치. 다음 구현자는 결정을 따를지 코드를 따를지 되묻게 됨 → 사용자 정합 필요. 두 옵션의 trade-off:
- (a) 결정대로
java-test-fixtures마이그레이션 — fixture 가 main classpath 로 새지 않음을 plugin 이 강제. 비용: source set 분리 + 모든 fixture 이동. - (b) 결정을 코드 현실(
fixtures/package)로 갱신 — 가볍지만 누수 차단은 별도 ArchUnit rule(noClasses().that().resideIn("..fixtures..").should().dependOnClassesThat()..., §Claims To Verify 2행)에 의존. - 정합 전까지 D6 는
UNSUPPORTED_DECISION유지. 상세 → §AuditFIXTURE_LAYOUT_DRIFT. RESOLVED(2026-06-19): 옵션 (b) 채택 —fixtures/package 유지 + ArchUnit 누수 ruleproduction_code_does_not_depend_on_test_fixtures추가. §결정 사항 2026-06-19 / §Audit.
- (a) 결정대로
3. Sample fixture prod 누수 방어 (테스트 계약) — 메커니즘 DRIFT
Trace: §테스트 계약 "sample fixture prod leakage 검사". §2 코드 대조에서 drift 확정.
- 결정 명세:
@ActiveProfiles("prod"|"staging")테스트의 ApplicationContext 에서 sample package class 0개 + ArchUnit 으로@ActiveProfilesprod/staging test 의features.sampleimport 금지. - 실제 코드(
actually-implemented): 누수 방어는 build-time ArchUnit ruleproduction_code_does_not_depend_on_sample_portfolio(CleanArchitectureTest.java:576) — production scope ↛sample-portfoliomodule 의존 차단. sample 은features.samplepackage 가 아니라sample-portfoliomodule(test classpath only).application-prod.yml/application-staging.yml없음. - UNSUPPORTED_IMPL_DECISION + DRIFT: 명세 메커니즘(runtime
@ActiveProfiles+ ApplicationContext bean count)과 실제(build-time module-dependency ArchUnit rule)가 다름. trade-off: build-time module rule 은 compile graph 를 막아 더 이르게 실패하지만 runtime profile-conditional 활성 여부는 검증 못 함; runtime check 는 실제 활성 bean 을 보지만 늦게 실패. 정합 권고 → §AuditSAMPLE_GUARD_MECHANISM_DRIFT. RESOLVED(2026-06-19): build-time module rule 유지로 확정(옵션 b). runtime@ActiveProfilescheck·prod/staging yml 미추가 — 위 trade-off 의 "이른 실패 + compile graph 차단" 을 우선. §결정 사항 2026-06-19.
4. Contract 도구 (D5) — 도구 DRIFT
Trace: D5 (cross-branch-reference; 도구 owner 는
feature-contract-verification-test-suite).
- 결정 명세: JSON snapshot =
approvaltests-java(또는 자체) + OpenAPI drift = springdoc 생성 vs checked-in diff. - 실제 코드(
actually-implemented):approvaltests의존성 없음. OpenAPI snapshot =sample-portfolio/.../openapi/OpenApiSnapshotTest.java. contract/ 디렉터리는 ArchUnit/custom 기반 contract test. - UNSUPPORTED_IMPL_DECISION + DRIFT: 본 branch 는 도구 owner 아님(verification branch 위임) — 도구 결정 정합은 그 branch 가 수행. 본 노트는 drift 만 surface → §Audit
CONTRACT_TOOL_DRIFT.
5. CI gate 매핑 (테스트 계약 contract-change
Trace: §테스트 계약 "contract-change 동반 test 검사" · "blueprint-change 동반 architecture test 검사".
- git diff regex 기반 CI step 2종(registry/owner-branch 변경 ↔
src/test/**/contract/변경 동반, blueprint/enforcement 변경 ↔src/test/**/architecture/변경 동반).planned— §2 ground truth 에서 dual-mode CI matrix workflow 미발견, canonical doc 도 "CI matrix 미작성" 명시. - UNSUPPORTED_IMPL_DECISION: git diff regex 의 false positive/negative(파일 rename, 신규 registry 파일 추가 시 false miss). trade-off: regex 는 가볍지만 경로 변경에 취약 → §Claims To Verify 6행으로 검증 위임.
DEFERRED(2026-06-19, planned 유지): ca-tmpl 에.github/workflows부재 — CI gate 신설을 이번 구현에서 보류. 5분 budget 측정·companion-change gate 는 raw/branch-notes/feature-ci-quality-gates-contract 와 함께 후속. 본 branch 의 로컬 강제(§테스트 계약 #3·#4 + slice rule)는 ArchUnit 으로 완료. §결정 사항 2026-06-19.
Audit & Findings
/branch-spec(2026-06-15) ca-tmpl 코드 ground truth 대조에서 발견한 결정↔코드 drift. 사용자 작성 결정 영역이므로 자동 rewrite 하지 않고 정합 권고만 기록. 등급은 §2 직접 grep 으로 확정.
| Finding | 결정(노트) | 코드(ca-tmpl ground truth) | 권고 |
|---|---|---|---|
FIXTURE_LAYOUT_DRIFT (D6) |
src/testFixtures/java/<feature>/ source set + *Fixture.java/*Mother.java |
java-test-fixtures 플러그인·src/testFixtures 없음. fixtures = src/test/java/.../fixtures/ package (sample-portfolio/.../fixtures/SamplePortfolioFixture.java), architecture/violations/.../*Fixture.java. *Mother.java 없음 |
RESOLVED (2026-06-19, 옵션 b): 현행 fixtures/ package 유지로 확정 + main-classpath 누수 차단 rule CleanArchitectureTest.production_code_does_not_depend_on_test_fixtures 추가. src/testFixtures 결정 superseded — §결정 사항 2026-06-19 |
CONTRACT_TOOL_DRIFT (D5) |
approvaltests-java JSON snapshot + springdoc OpenAPI diff |
approvaltests 의존성 없음. OpenAPI snapshot = OpenApiSnapshotTest.java. 도구 owner = feature-contract-verification-test-suite |
도구 결정 정합은 verification branch 에서; 본 노트는 surface 만 |
SAMPLE_GUARD_MECHANISM_DRIFT (테스트 계약) |
@ActiveProfiles("prod"/"staging") + ApplicationContext bean 0개 + features.sample import 금지 |
build-time ArchUnit production_code_does_not_depend_on_sample_portfolio(CleanArchitectureTest.java:576); sample-portfolio module(≠ features.sample package); prod/staging yml 없음 |
RESOLVED (2026-06-19, 옵션 b): build-time ArchUnit module rule 유지로 확정 — runtime @ActiveProfiles check 미채택, §테스트 계약 #3 명세를 build-time 기준으로 갱신. §결정 사항 2026-06-19 |
D2_NOW_IMPLEMENTED (positive) |
D2 UNSUPPORTED + Claims planned; canonical skeleton-governance-...-scorecard.md "실제 구현 내용: 없음" |
architecture-test 실제 구현됨(CleanArchitectureTest.java, DisabledAdapterArchitectureTest.java, violation fixtures 까지) |
canonical project doc 의 "documented-only/없음" 서술이 stale — /ingest 전 actually-implemented 로 갱신 권고 |
DIR_LEVEL_NAME_DRIFT (D3 / Test Level Matrix) |
D3: "contract test 는 Testcontainers 금지" + Test Level Matrix 가 contract/integration 을 별개 level 로 분리 | contract/outbox/Outbox*ContractTest 가 @Testcontainers 사용(OutboxAppendTransactionalContractTest.java:31) — 실체는 integration-level test 가 contract/ 디렉터리에 mis-filed (D3 와 표면상 충돌하나 본질은 위치/명칭 drift, 계약 위반 아님) |
RESOLVED (2026-06-19): Task 1 에서 outbox Testcontainers tests 를 bootstrap/integration/outbox/ 로 재분류 완료. contract/ tree 에 Testcontainers 의존 없음 — TestTaxonomyArchitectureTest.contract_level_tests_have_no_testcontainers_dependency() PASS 로 검증됨 |
엣지·실패·의존
R4 캡처. 정상 경로 외 구현 중 부딪힐 실패/엣지 + 다른 계약 의존을 미리 열거.
- 실패·엣지 경로:
- ArchUnit rule typo → false negative(silent pass): 패키지 패턴 오탈자면 위반을 못 잡고 통과. 방어 = 의도적 violation fixture(
architecture/violations/.../*Fixture.java)로 rule 이 실제 fail 하는지 메타검증. ca-tmpl 에 이미 존재(actually-implemented). §Claims To Verify 4행. @WebMvcTest+ Spring Security → context 적재 비용 증가: security filter chain 스캔으로 slice 속도 이점 감소, 5분 budget(D4) 위협. 방어 =@Import(SecurityConfig)수동 제어.@DataJpaTestH2 기본값 ↔ Testcontainers real DB 불일치: slice 가 H2, integration 이 Postgres 면 query 동작 차이. 방어 =@AutoConfigureTestDatabase(replace=NONE)(planned—@DataJpaTest미도입).- 두 slice annotation 한 클래스 혼용: Spring 공식 "not supported"(SB-SLICE-C2) — context 가 의도와 다르게 작동. 방어 = ArchUnit rule(
planned, §Claims To Verify 3행). - contract-change CI regex false miss: 파일 rename / 신규 registry 파일이면 동반 test 강제를 우회. §Claims To Verify 6행.
- fixture 누수: fixture 가 main classpath 로 새면 prod 빌드 오염. D6 drift 로 현재 plugin 강제 부재 → ArchUnit rule 의존(§구현 가이드 2).
- smoke level 실패: bootstrap context 적재 실패(컨테이너 미기동/포트 충돌) → fail-fast; sample removal 미완 상태로 startup 시 smoke fail. Test Level Matrix 가 smoke Testcontainers 를
optional로 두어 분기 모호 → 아래 gate 귀속 규칙으로 해소.
- ArchUnit rule typo → false negative(silent pass): 패키지 패턴 오탈자면 위반을 못 잡고 통과. 방어 = 의도적 violation fixture(
- level → CI gate 귀속 (D4 보강): D4 의 5min gate 는
{unit, contract, architecture}한정.slice·smoke중 외부 의존(Testcontainers/real provider)이 있는 것은 integration matrix gate(시간 무제한), 없는 것은 5min gate. 즉 gate 분기 기준은 level 이름이 아니라 외부 의존 유무. (@DataJpaTestH2-only slice = 5min gate, Testcontainers smoke = integration gate.) - 다른 계약 의존:
- raw/branch-notes/feature-ci-quality-gates-contract — flaky quarantine sunset(14일) 정책 SSOT. 본 branch D8 은 bucket 분리만 위임. 그 sunset/ownership 정책이 바뀌면 D8 영향.
- raw/branch-notes/feature-contract-verification-test-suite — contract 도구 선택 + 11 gate / snapshot 로직 SSOT. 본 branch D5 가 consume. 도구 결정 변경 시 §구현 가이드 4 / §Audit
CONTRACT_TOOL_DRIFT갱신. - raw/branch-notes/feature-skeleton-package-blueprint-contract · raw/branch-notes/feature-architecture-enforcement-rules — architecture-test(D2)가 강제하는 package blueprint / boundary rule 의 정의 owner. blueprint 가 바뀌면
CleanArchitectureTestrule 갱신 필요. - raw/branch-notes/feature-operational-error-observability-foundation · raw/branch-notes/feature-log-management-contract · raw/branch-notes/feature-env-driven-runtime-configuration — contract-test(D1)가 보호하는 envelope/error/log/env 계약 owner. 이들 결정 변경 시
src/test/**/contract/동반 변경 필요(§테스트 계약 contract-change). - raw/branch-notes/feature-sample-removal-adoption-contract — sample-portfolio 누수 방어 대상(§테스트 계약 / §구현 가이드 3)의 sample-off / adoption 결정 owner.
테스트 계약
- contract-change 동반 test 검사: PR diff에 다음 중 1개라도 변경이 포함되면(
ca-tmpl/docs/registries/*.yaml,feature-operational-error-observability-foundation결정 사항,feature-log-management-contract결정 사항,feature-env-driven-runtime-configuration결정 사항) PR diff에src/test/**/contract/디렉터리의 file 변경도 포함되어야 함. 측정 방법: GitHub Actions stepgit diff --name-only origin/main..HEAD | grep -E "(registries/.*\.yaml|feature-(operational-error|log-management|env-driven).*\.md)"결과 != empty이고git diff --name-only origin/main..HEAD | grep "src/test/.*contract/"결과 == empty이면 fail. - blueprint-change 동반 architecture test 검사: PR diff에
feature-skeleton-package-blueprint-contract.md또는feature-architecture-enforcement-rules.md변경이 포함되면src/test/**/architecture/디렉터리의 file 변경도 포함되어야 함. 측정 방법: 동일 git diff regex 조합. 불일치 시 fail. - sample fixture prod leakage 검사: production profile(
application-prod.yml,application-staging.yml)이 활성된 SpringBootTest 또는 Testcontainers integration test에서features.sample.package의 class가 ApplicationContext에 등록되거나 fixture로 사용되면 fail. 측정 방법:@ActiveProfiles("prod")또는@ActiveProfiles("staging")테스트 실행 후ApplicationContext.getBeanNamesForType(...)결과에서 sample package class 0개여야 함. 또한 ArchUnit으로@ActiveProfilesvalue가 prod/staging인 test class는 sample package import 금지. (RESOLVED 2026-06-19: ca-tmpl 채택 메커니즘은 build-time ArchUnit module ruleproduction_code_does_not_depend_on_sample_portfolio로 확정 — 위 runtime@ActiveProfiles/ApplicationContext bean-count 명세는 미채택. §AuditSAMPLE_GUARD_MECHANISM_DRIFT/ §결정 사항 2026-06-19) - contract/architecture test가 Testcontainers에 의존하면 실패. (ENFORCED 2026-06-19:
TestTaxonomyArchitectureTest.contract_and_architecture_tests_do_not_depend_on_testcontainers—..contract../..architecture..코퍼스에 위반 없음 +..integration..positive-control 로 발화 증명. manual-importer 사용 이유는@AnalyzeClasses(DoNotIncludeTests)가 test bytecode 미포함이기 때문.)
검증해야 할 주장
공식 문서나 사례는 근거지만, 내 프로젝트에서의 동작을 자동으로 보장하지 않는다. 구현 전/중/후에 실제로 검증해야 하는 주장을 분리.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| unit+contract+architecture test 합산이 5분 이내 완료 가능 | Testcontainers 공식 자료 (TC-OFFICIAL-C4) 는 IDE 실행 가능성만 보장, 시간 budget 정량 보장 없음 |
CI workflow 에서 unit+contract+architecture job 실행 시간 측정 + 5분 초과 시 알림 |
needs-confirmation |
src/testFixtures/java/<feature>/ source set 이 도메인 분리를 실제로 강제 |
Gradle source set 자체는 fixture 위치만 강제, 도메인 분리는 별도 ArchUnit rule 필요. DRIFT: 현재 코드는 testFixtures 미사용(fixtures/ package) → 이 Claim 은 결정(b) 채택 시에만 유효 |
ArchUnit noClasses().that().resideIn("..testFixtures..").should().dependOnClassesThat().resideInAPackage("..features.[^.]+..") 룰 작성 후 위반 검출 |
planned |
@WebMvcTest/@DataJpaTest 와 hex slice 가 한 클래스에서 혼용되지 않음 |
Spring 공식(SB-SLICE-C2)은 혼용을 "not supported" 로 명시하나 hex slice 와의 분리는 별도 — 혼용 시 context 확장이 의도와 다르게 작동 가능 | ArchUnit / custom test 로 @WebMvcTest 또는 @DataJpaTest 가 붙은 class 가 hex slice 구성 요소 (use case interface 등) 와 같은 file 에 없는지 검사 |
planned |
| ArchUnit boundary rule 이 ca-tmpl package blueprint 위반을 모두 탐지 | ArchUnit DSL 표현력 한계 가능 + rule typo 시 silent false negative | 의도적 boundary 위반 코드(violation fixture)를 추가하고 ArchUnit 이 fail 하는지 확인 — ca-tmpl 에 architecture/violations/.../*Fixture.java 이미 존재(actually-implemented) |
locally-verified(메커니즘 존재) / planned(전수성) |
| sample-portfolio fixture 가 production scope 에서 제거됨 | 노트 명세(@ActiveProfiles("prod") ApplicationContext check)와 실제 메커니즘(build-time ArchUnit module rule)이 다름 — §Audit SAMPLE_GUARD_MECHANISM_DRIFT |
CleanArchitectureTest.production_code_does_not_depend_on_sample_portfolio(L576) 가 production↛sample-portfolio 의존을 차단하는지 확인 — actually-implemented |
locally-verified |
| contract-change 동반 test 검사 git diff regex 가 false positive/negative 없음 | regex 가 file 경로 변경 (rename) 또는 새 registry 파일 추가 시 false miss 가능 | 의도적으로 registry yaml 만 수정한 PR 과 src/test/contract 만 수정한 PR 각각 생성 → CI 동작 verify | planned |
관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
/coverage가 채우는 생성물 — 손으로 유지하지 않는다. governing 문서(governing_docs: wiki/projects/ca-tmpl/skeleton-governance-registry-verification-test-scorecard §Test taxonomy, wiki/projects/ca-tmpl/sample-fixture-and-adoption §Sample fixture)가 요구하는 관심사를 본 브랜치가 빠짐없이 덮는지의 결과. 기준:rules/coverage-gate.md. 상태:covered-here(이 브랜치 결정) /delegated(다른 owner 브랜치) /missing(아무도 안 맡음 → Blocking).
생성:
/coverage(coverage-auditor, 2026-06-15). governing 2종 + sibling 브랜치 + ca-tmpl 코드 대조. 판정: Covered (Blocking 0).
| 관심사 | 상태 | owner | 심각도 | 근거 |
|---|---|---|---|---|
| 6-level test taxonomy 정의 (unit/contract/architecture/slice/integration/smoke) | covered-here | — | — | D1·D2·D7 + Test Level Matrix; ca-tmpl actually-implemented |
| Testcontainers 적용 범위 (integration부터 강제 / 타 level 금지) | covered-here | — | ⚪ Advisory | D3; 단 contract/outbox/Outbox*ContractTest 가 @Testcontainers 사용 → §Audit DIR_LEVEL_NAME_DRIFT |
fixture 격리 방법 (source set vs fixtures/ package) |
covered-here | — | — | D6(UNSUPPORTED, drift) → §Audit FIXTURE_LAYOUT_DRIFT |
| 5min CI budget 정책 | covered-here | — | — | D4(team-policy) + §Claims 1행 |
| 6-level 디렉터리 강제 메커니즘 | covered-here (planned) | — | — | §구현 가이드 1 UNSUPPORTED_IMPL_DECISION + §Claims 2행 |
| sample fixture production 누수 방어 메커니즘 | covered-here | — | — | §테스트 계약 + §구현 가이드 3 → §Audit SAMPLE_GUARD_MECHANISM_DRIFT |
| flaky test ownership / quarantine 정책 | delegated | raw/branch-notes/feature-ci-quality-gates-contract | — | D8 위임 (§엣지·§결정 사항 링크; SSOT = sunset 14일) |
| contract test 도구 선택 (snapshot/OpenAPI) | delegated | raw/branch-notes/feature-contract-verification-test-suite | — | D5 위임 (§엣지 링크) → §Audit CONTRACT_TOOL_DRIFT |
| package blueprint / boundary rule 정의 | delegated | raw/branch-notes/feature-skeleton-package-blueprint-contract · raw/branch-notes/feature-architecture-enforcement-rules | — | D2 위임 (§엣지 링크) |
| sample fixture 종류 + 12 scenario + 6-field minimum | delegated | raw/branch-notes/feature-sample-domain-contract-fixture | — | governing sample-fixture-and-adoption.md §Sample fixture SSOT — raw/branch-notes/feature-sample-domain-contract-fixture |
| sample-off / adoption 절차 (dual-mode CI matrix · adoption checklist) | delegated | raw/branch-notes/feature-sample-removal-adoption-contract | — | governing §Sample-off SSOT — raw/branch-notes/feature-sample-removal-adoption-contract (§엣지 링크) |
마주친 문제
-
OperationalContractRuntimeTest2건 실패 (pre-existing)- 원인:
@WebMvcTestSpring context load 실패 (ConversionFailedException,LenientObjectToEnumConverterFactory). 이 branch 변경과 무관 — 해당 파일은b314a99(readme refactoring) 이전 커밋에서 유래하며 본 branch 에서 수정하지 않음. - 검증(definitive, controller 2026-06-19):
git stash push -u로 본 branch 변경(tracked+untracked) 전부 제거 → working tree == clean HEADa0534b9확인 →./gradlew :app-bootstrap:test --tests '*OperationalContractRuntimeTest'실행 → clean HEAD 에서도 동일하게 2건 실패(ConversionFailedException/LenientObjectToEnumConverterFactory) →git stash pop으로 변경 원복. 본 branch 도입 전 코드에서 재현되므로 pre-existing 확정. - root-cause (2026-06-20):
application.yml:339의client-ip-mode: ${APP_RATE_LIMIT_CLIENT_IP_MODE}가 기본값 없는 placeholder —.env없는./gradlew test에서 미해석 리터럴이RateLimitClientIpModeenum 변환 실패 → context load fail. 상세 → raw/errors/webmvctest-slice-configprops-enum-placeholder-no-default-2026-06-20. - 해결 (2026-06-20, 사용자 요청으로 본 세션에서 수정 — rate-limit 관심사라 별도 커밋 권장):
application.yml을 레지스트리 선언값대로${APP_RATE_LIMIT_CLIENT_IP_MODE:remote-addr-only}로 갱신. 검증:--tests '*OperationalContractRuntimeTest'PASS,verifyEnvKeys: OK, 전체:app-bootstrap:test--rerun-tasks3회 연속 GREEN.
- 원인:
-
TestTaxonomyArchitectureTest전체 스위트 flaky/vacuous (2026-06-20)- 증상: 단독 6/6 PASS 인데 전체 스위트 첫 실행에서 positive-control 3건 간헐 FAIL(코퍼스 빈 채로). clean-check 는 빈 코퍼스에서 silent vacuous-pass 위험.
- 원인: positive-control 코퍼스가
importPackages(String)static 필드 — 대형 스위트/stale build 에서 빈 코퍼스 반환 가능. 상세 → raw/errors/archunit-importpackages-empty-vacuous-stale-build-2026-06-20. - 해결: positive-control/over-block 코퍼스를
importClasses(Class…)(결정적)로 전환 + 슬라이스 fixturepublic승격 + 전용TestcontainersUsingFixture(..taxonomyfixtures..); clean-check 2건은importPackages유지하되 non-vacuity 가드(corpus.size()>0) 추가. 검증: 전체--rerun-tasks3회 연속 GREEN.
묶음
- raw/company-tech-blogs/ci-flaky-test-quarantine-spotify-google
- raw/company-tech-blogs/test-pyramid-vs-trophy-kent-dodds
- raw/official-docs/dx-testcontainers-java-best-practices
- raw/official-docs/governance-archunit-official
- raw/official-docs/spring-boot-test-slices-webmvctest-datajpatest-official
- raw/official-docs/test-taxonomy-practical-pyramid-fowler
- raw/official-docs/test-taxonomy-testcontainers-official
- raw/errors/archunit-importpackages-empty-vacuous-stale-build-2026-06-20
- raw/errors/webmvctest-slice-configprops-enum-placeholder-no-default-2026-06-20
Phase C2 (ca-implementer) 실 코드 작업 완료 (2026-06-19).
구현 산출물 (2026-06-19 ca-implementer)
Task 1 — Testcontainers 테스트 재분류 (contract/ → integration/)
| 파일 | 작업 |
|---|---|
bootstrap/contract/DistributedLockProviderContractTest.java |
삭제 |
bootstrap/contract/IdempotencyUniqueScopeContractTest.java |
삭제 |
bootstrap/contract/outbox/Outbox* (4개) |
삭제 |
bootstrap/integration/DistributedLockProviderContractTest.java |
생성 (package 변경만) |
bootstrap/integration/IdempotencyUniqueScopeContractTest.java |
생성 (JavaDoc FQN 참조 수정) |
bootstrap/integration/outbox/Outbox* (4개) |
생성 (package + 주석 수정) |
bootstrap/integration/package-info.java |
생성 |
bootstrap/integration/outbox/package-info.java |
생성 |
Task 2 — TestTaxonomyArchitectureTest.java 생성 (manual-importer pattern)
- Rule:
contract_and_architecture_tests_do_not_depend_on_testcontainers(allowEmptyShould=true) - Meta-tests: contract corpus (isFalse, non-vacuity 가드) · architecture corpus (isFalse, non-vacuity 가드) · TestcontainersUsingFixture positive control (isTrue)
- plain
@Test(NOT@AnalyzeClasses) — 이유:@AnalyzeClasses는DoNotIncludeTests로 test bytecode 미포함 - 하드닝 (2026-06-20): positive-control/over-block 코퍼스를
importClasses(Class…)결정적 import 로 전환(flaky/vacuous 수정 — §마주친 문제). clean-check 2건만importPackages+corpus.size()>0가드. positive-control 용taxonomyfixtures/TestcontainersUsingFixture.java신설(..contract../..architecture..밖), 슬라이스 fixturepublic승격.
Task 3 — slice_tests_do_not_mix_two_spring_slice_annotations rule + fixtures
- FQN string 참조 패턴 (
"org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest"등) — compile 의존 없음 violations/slice/MixedSliceAnnotationsFixture.java(positive control)allowed/slice/SingleSliceWebMvcFixture.java(over-block guard)
Task 4 — CleanArchitectureTest.java 에 @ArchTest 추가 + fixtureleak fixtures
@ArchTest static final ArchRule production_code_does_not_depend_on_test_fixturesviolations/fixtureleak/LeakyProductionConsumerFixture.java+violations/fixtureleak/fixtures/LeakedTestFixture.java- meta-test:
TestTaxonomyArchitectureTest.fixture_leak_rule_fires_on_production_depending_on_fixture()—CleanArchitectureTest의@ArchTestfield 를 직접 참조해 evaluate
Task 5 — 검증 결과
| Command | Result |
|---|---|
./gradlew :app-bootstrap:test --tests '*TestTaxonomyArchitectureTest' |
6/6 PASS |
./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest' |
PASS |
./gradlew verifyCleanArchitectureDependencies |
BUILD SUCCESSFUL |
./gradlew verifyEnvKeys |
OK (99 keys, 74 required placeholders covered) |
./gradlew :app-bootstrap:test (full, --rerun-tasks) |
3회 연속 GREEN (2026-06-20, application.yml fix + test 하드닝 후 — 직전 2-fail 은 §마주친 문제에서 해소) |
추가 변경 (2026-06-20, 본 세션):
application.ymlrate-limit default fix(production resource),TestTaxonomyArchitectureTest하드닝,taxonomyfixtures/TestcontainersUsingFixture신설, 슬라이스 fixturepublic승격.
오류 기록 (본 feature 작업 중 발생)
- raw/errors/webmvctest-slice-configprops-enum-placeholder-no-default-2026-06-20 —
@WebMvcTest슬라이스가 기본값 없는 enum placeholder 로 context load 실패(rate-limitclient-ip-mode). 본 세션에서 root-cause + fix. - raw/errors/archunit-importpackages-empty-vacuous-stale-build-2026-06-20 —
importPackages(String)static 코퍼스가 대형 스위트에서 빈 채로 반환 → positive-control flaky + clean-check vacuous-pass. importClasses + non-vacuity 가드로 해결.
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- raw/interviews/archunit-manual-importer-vs-analyzeclasses —
@AnalyzeClasses(importOptions=DoNotIncludeTests)vsnew ClassFileImporter()를 언제 쓰나? test bytecode 를 rule 의 대상으로 삼고 싶을 때 왜 manual importer 가 필요한가? (2026-06-19 작성)
Blog topics (이 작업에서 나온 글감)
- raw/blog-topics/test-taxonomy-archunit-enforcement-2026-06-19 — 테스트 분류를 문서 에서 빌드가 강제하는 import-graph 규칙 으로 옮긴 사례(Testcontainers ban + slice-mixing ban + fixture-leak guard + manual-importer/positive-control). (2026-06-19 작성)
관련 일일 노트
[[raw/daily-notes/2026-06-19]]— ca-implementer Phase C2 실 코드 작업 완료 일자
완료 후 wiki 추출 대상
wiki/projects/ca-tmpl/skeleton-governance-registry-verification-test-scorecard.md의 Test taxonomy(§29 G-G) canonical section +wiki/projects/ca-tmpl/sample-fixture-and-adoption.md의 fixture 누수 방어 section. (구 경로wiki/projects/ca-skeleton-operational-contract.md는 현재 cluster 분리됨.)
완료 후 정리
머지/종료 시점에 채움.
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목:locally-verified항목:prod-verified항목:
- 추출하지 않을 항목 (planned / documented-only / abandoned):