init: 클린 기반 auth 서버 설계
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
# 2026-05-04 멀티모듈 aggregate JaCoCo 리포트 도입
|
||||
|
||||
## 한 줄 결론
|
||||
|
||||
`./gradlew jacocoAggregateReport` 태스크를 도입하여, 모든 서브프로젝트의 jacoco execution data를 단일 리포트로 합쳤다. **bootstrap의 통합 테스트가 다른 모듈을 실행한 흔적까지 반영되어, presentation 모듈 라인 커버리지가 24.8% → 63.7% (aggregate 기준)으로 정직하게 드러났다.**
|
||||
|
||||
## 변경 범위
|
||||
|
||||
- `build.gradle` 루트에 `jacoco` 플러그인 적용
|
||||
- `jacocoAggregateReport` 태스크 신규 등록 (모든 서브프로젝트 `test.exec` 합산)
|
||||
- `archiveCoverageReport` 태스크가 aggregate 리포트도 자동 보관하도록 확장
|
||||
|
||||
## Aggregate 결과 (전체)
|
||||
|
||||
| Counter | covered/total | % |
|
||||
|---|---|---|
|
||||
| LINE | 684/880 | **77.7%** |
|
||||
| BRANCH | 165/252 | 65.5% |
|
||||
| METHOD | 183/214 | 85.5% |
|
||||
| CLASS | 50/53 | 94.3% |
|
||||
|
||||
## 모듈별 단위 측정 vs Aggregate 비교 (라인)
|
||||
|
||||
| 패키지 | 모듈 단위 | Aggregate | 차이 (통합 테스트 효과) |
|
||||
|---|---|---|---|
|
||||
| `presentation/support/exception` | 24.8% | **63.7%** | +38.9%p |
|
||||
| `presentation/support/response` | (포함됨) | **100.0%** | — |
|
||||
| `application/support/logging` | 90.9% | 90.9% | (이미 jqwik으로 직접 커버) |
|
||||
| `application/support/exception` | 71.9% | 71.9% | — |
|
||||
| `config/web` (ApiErrorController 등) | 87.0% | 87.0% | — |
|
||||
| `config/auth/security` | 85.7% | 85.7% | — |
|
||||
|
||||
**해석:** presentation 핸들러의 진짜 커버리지는 단위 측정이 보여주던 24.8%가 아니라 aggregate 기준 63.7%다. Tier 1 임계치(95%)까지의 격차도 이제 정확하게 보인다 — 31.3%p.
|
||||
|
||||
## 자동 검출 가능해진 회귀
|
||||
|
||||
- **presentation 핸들러의 진짜 커버리지가 운영 게이트에 노출됨** — 이전에는 단위 측정만 보여 통합 테스트 효과가 가려졌음
|
||||
- 새 모듈 추가 시 aggregate 리포트가 자동으로 포함
|
||||
|
||||
## 측정 조건
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| BEFORE | 모듈별 jacocoTestReport만 존재 |
|
||||
| AFTER | 추가로 `build/reports/jacoco/aggregate/html/index.html` 생성 |
|
||||
| 측정 명령어 | `./gradlew clean jacocoAggregateReport` |
|
||||
| 합산 대상 | 모든 서브프로젝트 `{module}/build/jacoco/test.exec` |
|
||||
| Exclusion | `build.gradle`의 `jacocoExclusions` 그대로 적용 |
|
||||
|
||||
## 한계 / 미해결
|
||||
|
||||
- **63.7%는 여전히 Tier 1(95%) 미달** — 다음 사건(2번): presentation 핸들러 단위 테스트 또는 통합 테스트 추가로 95% 도달.
|
||||
- **PIT는 aggregate 미지원** — PIT는 모듈별로만 측정 가능. 통합 테스트가 bootstrap에 있으면 presentation의 PIT mutation kill은 여전히 낮게 보임. 다음 사건(5번)에서 처리.
|
||||
|
||||
## 첨부
|
||||
|
||||
- AFTER 스냅샷: `coverage-history/v0.0.1-SNAPSHOT/2026-05-04-aggregate-after/`
|
||||
- 모듈별: `{module}/jacoco/html/index.html`
|
||||
- 통합: `aggregate/html/index.html`
|
||||
@@ -0,0 +1,63 @@
|
||||
# 2026-05-04 application Tier 1 95% 도달
|
||||
|
||||
## 한 줄 결론
|
||||
|
||||
`application.support.exception` 패키지 **100%**, `application.support.logging` 패키지 **96.3%**로 Tier 1 임계치(95%) 통과. dead code 1개(`LogSanitizer.reason`) 제거로 자연 정리 동시 수행. SHA-256 NoSuchAlgorithmException catch 2줄은 환경 의존이라 자연 미커버 인정.
|
||||
|
||||
## 변경 범위
|
||||
|
||||
신규 단위 테스트 3개:
|
||||
- `BusinessExceptionTest` — 4개 protected 생성자 + getErrorCode 직접 호출
|
||||
- `ErrorCodeEnumTest` — `CommonErrorCode`/`AuthErrorCode` 모든 enum 값에 대해 code()/message() 단언 (parameterized)
|
||||
- `LogSanitizerEdgeCaseTest` — single-arg normalize, malformed email, 비숫자 IPv4 옥텟 등 jqwik이 도달하기 어려운 분기
|
||||
|
||||
코드 정리:
|
||||
- `LogSanitizer.reason(String)` 메서드 제거 — 어디서도 호출되지 않는 dead code
|
||||
|
||||
## Before / After 수치 (application 모듈 단위 측정)
|
||||
|
||||
### Tier 1 패키지
|
||||
|
||||
| 패키지 | BEFORE LINE | AFTER LINE | 변화 |
|
||||
|---|---|---|---|
|
||||
| `application/support/exception` | 40.6% | **100.0%** | +59.4%p |
|
||||
| `application/support/logging` (LogSanitizer) | 90.9% | **96.3%** | +5.4%p |
|
||||
|
||||
### application 모듈 전체
|
||||
|
||||
| Counter | BEFORE | AFTER |
|
||||
|---|---|---|
|
||||
| LINE | 70.9% | 83.3% |
|
||||
| BRANCH | 78.9% | 84.2% |
|
||||
| METHOD | 63.6% | 81.5% |
|
||||
| CLASS | 81.2% | 87.5% |
|
||||
|
||||
## 자동 검출 가능해진 회귀
|
||||
|
||||
- `BusinessException` 4개 생성자 중 하나가 깨지면 `application:test`에서 즉시 실패
|
||||
- ErrorCode enum 인스턴스 추가 시 자동으로 단언 대상에 포함
|
||||
- LogSanitizer 비숫자 옥텟 처리 회귀 즉시 검출
|
||||
|
||||
## 잔여 미커버 (2줄, 자연 인정)
|
||||
|
||||
`LogSanitizer.sha256Hex`의 `NoSuchAlgorithmException` catch 블록 2줄:
|
||||
|
||||
```java
|
||||
} catch (NoSuchAlgorithmException exception) {
|
||||
throw new IllegalStateException("SHA-256 digest is not available.", exception);
|
||||
}
|
||||
```
|
||||
|
||||
JDK가 SHA-256을 미지원하는 환경은 표준 JRE에서 발생하지 않으므로 단위 테스트로 도달 불가. PIT에서도 mutation 의미 없음. **운영 가시성 안전망(throw IllegalStateException)으로서 유지하되 측정 게이트의 미커버는 인정.**
|
||||
|
||||
## 측정 조건
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| BEFORE 커밋 | `7756028` (presentation Tier 1 직후) |
|
||||
| AFTER 커밋 | (본 커밋) |
|
||||
| 측정 명령어 | `./gradlew clean test jacocoTestReport` |
|
||||
|
||||
## 첨부
|
||||
|
||||
- AFTER 스냅샷: `coverage-history/v0.0.1-SNAPSHOT/2026-05-04-application-tier1/`
|
||||
@@ -0,0 +1,48 @@
|
||||
# 2026-05-04 jacocoExclusions 정밀화
|
||||
|
||||
## 한 줄 결론
|
||||
|
||||
`**/config/**` 광역 제외를 **클래스명 컨벤션 기반**(`*Configuration`/`*Config`/`*Properties`/`*Application`)으로 좁혀, **bootstrap 모듈 라인 커버리지가 정상 운영 빌드 기준 0% → 86.8%로 노출**되도록 했다. 그동안 핵심 핸들러(ApiErrorController, SecurityResponseExceptionHandler 등)가 측정에서 통째로 빠져 있던 문제를 해소했다.
|
||||
|
||||
## 변경 범위
|
||||
|
||||
- `build.gradle`의 `jacocoExclusions` 패턴 변경
|
||||
- 제거: `**/AuthApplication.class`, `**/config/**`
|
||||
- 추가: `**/*Application.class`, `**/*Configuration.class`, `**/*Config.class`, `**/*Properties.class`
|
||||
- `docs/testing-coverage-policy.md` 제외 목록 업데이트
|
||||
- 코드 변경 없음, 테스트 변경 없음 (측정 정책만 수정)
|
||||
|
||||
## Before / After 수치 (운영 빌드 기준)
|
||||
|
||||
| 모듈 | BEFORE 라인 | AFTER 라인 | BEFORE 메서드 | AFTER 메서드 |
|
||||
|---|---|---|---|---|
|
||||
| `application` | 70.9% | 70.9% | 63.6% | 63.6% |
|
||||
| `presentation` | 24.8% | 24.8% | 25.0% | 25.0% |
|
||||
| `infrastructure` | 81.5% | 81.5% | 79.2% | 79.2% |
|
||||
| `bootstrap` | **0.0%** | **86.8%** | **0.0%** | **95.2%** |
|
||||
|
||||
> bootstrap의 BEFORE 0%는 진짜 0%가 아니라 `**/config/**` exclusion 때문에 핸들러가 통째로 측정에서 빠진 결과였다. 사과-사과 비교는 이전 사건(2026-05-04 jqwik 도입)에서 80.7% → 80.3%로 확인되었으며, 본 사건은 그 진짜 점수가 운영 빌드에서도 보이게 만든 정책 정밀화다.
|
||||
|
||||
## 자동 검출 가능해진 회귀
|
||||
|
||||
- 핸들러를 직접 수정한 PR이 운영 게이트(`coverageGate`)에 잡히게 됨 — 이전에는 측정 자체가 안 되어 어떤 변경도 통과
|
||||
- 핵심 핸들러가 신규 분기를 추가하면 라인/브랜치 커버리지가 즉시 떨어져 보임
|
||||
|
||||
## 측정 조건 (재현용)
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| BEFORE 커밋 | `53e27cf` (Tier 4 제거 직후) |
|
||||
| AFTER 커밋 | (본 커밋) |
|
||||
| 측정 명령어 | `./gradlew clean test jacocoTestReport` |
|
||||
| 측정 조건 차이 | `jacocoExclusions` 정의만 변경, 그 외 모두 동일 |
|
||||
|
||||
## 한계 / 미해결
|
||||
|
||||
- **presentation 24.8%는 변하지 않음** — 통합 테스트가 bootstrap 모듈에 있어 presentation 모듈 단위 측정에 안 잡히는 구조적 문제. **다음 사건: aggregate 리포트 도입**으로 해결 예정.
|
||||
- bootstrap 86.8%지만 **Tier 1 임계치(95%)에는 아직 미달** — `**/config/**`에 가려져 있던 핸들러 일부에 단위 테스트가 추가로 필요.
|
||||
|
||||
## 첨부
|
||||
|
||||
- AFTER 스냅샷: `coverage-history/v0.0.1-SNAPSHOT/2026-05-04-exclusion-precision-after/`
|
||||
- 직전 BEFORE 스냅샷: `coverage-history/v0.0.1-SNAPSHOT/2026-05-04-jqwik-after/`
|
||||
@@ -0,0 +1,102 @@
|
||||
# 2026-05-04 jqwik 속성 기반 테스트 도입
|
||||
|
||||
## 한 줄 결론
|
||||
|
||||
보안 핵심(LogSanitizer)에 속성 기반 테스트를 추가하여 **application 모듈 분기 커버리지를 14.5% → 78.9%로 +64.4%p 끌어올렸으며**, 매퍼/JSON Pointer 변환에 대한 회귀 안전망(presentation 라인 3.4% → 24.8%)을 동시에 확보했다. 같은 측정 조건(exclusion 없음, 같은 명령어, 같은 도구 버전)에서 사과-사과 비교한 결과다.
|
||||
|
||||
## 변경 범위
|
||||
|
||||
- 신규 jqwik 의존성 도입 (1.9.3) — JUnit Platform에 `includeEngines` 추가
|
||||
- 신규 속성 테스트 4개 클래스, 총 30개 속성
|
||||
- `LogSanitizerPropertyTest` (11 속성, application 모듈)
|
||||
- `ApiErrorHttpStatusMapperPropertyTest` (5 속성, presentation 모듈)
|
||||
- `JsonPointerConversionPropertyTest` (8 속성, presentation 모듈)
|
||||
- `RequestBoundApiResultFactoryPropertyTest` (6 속성, bootstrap 모듈)
|
||||
- JaCoCo + PIT 플러그인 도입, Tier 기반 임계치 정책 수립
|
||||
- 영향 범위: application / presentation / bootstrap 모듈
|
||||
|
||||
## Before / After 수치 (사과-사과 측정)
|
||||
|
||||
### 라인 커버리지
|
||||
|
||||
| 모듈 | BEFORE | AFTER | 변화 |
|
||||
|---|---|---|---|
|
||||
| `application` | 42.3% | **70.9%** | **+28.6%p** |
|
||||
| `presentation` | 3.4% | **24.8%** | **+21.4%p** |
|
||||
| `infrastructure` | 81.5% | 81.5% | — |
|
||||
| `bootstrap` | 80.7% | 80.3% | -0.4%p (오차) |
|
||||
|
||||
### 분기 커버리지
|
||||
|
||||
| 모듈 | BEFORE | AFTER | 변화 |
|
||||
|---|---|---|---|
|
||||
| `application` | 14.5% | **78.9%** | **+64.4%p** |
|
||||
| `presentation` | 3.3% | **42.3%** | **+39.0%p** |
|
||||
| `infrastructure` | (브랜치 없음) | (브랜치 없음) | — |
|
||||
| `bootstrap` | 59.7% | 56.6% | -3.1%p |
|
||||
|
||||
### 메서드 / 클래스
|
||||
|
||||
| 모듈 | METHOD before → after | CLASS before → after |
|
||||
|---|---|---|
|
||||
| `application` | 41.8% → 63.6% | 75.0% → 81.2% |
|
||||
| `presentation` | 5.9% → 25.0% | 18.2% → 40.0% |
|
||||
| `infrastructure` | 79.2% → 79.2% | 100% → 100% |
|
||||
| `bootstrap` | 82.9% → 83.8% | 85.7% → 85.2% |
|
||||
|
||||
### PIT (단언 강도 측정)
|
||||
|
||||
| 모듈 | Mutations | Killed | Test Strength |
|
||||
|---|---|---|---|
|
||||
| `application` | 64 | **46 (72%)** | 82% |
|
||||
| `presentation` | 75 | 24 (32%) | **92%** (커버된 영역에서) |
|
||||
|
||||
> **PIT는 BEFORE에 측정되지 않았다.** PIT 플러그인 자체가 이 사건에서 처음 도입되었기 때문에 비교값은 없으며, AFTER의 절대 수치만 의미를 갖는다.
|
||||
|
||||
## 자동 검출 가능해진 회귀 시나리오
|
||||
|
||||
이전에는 단위 테스트가 0개였던 영역에 임의 입력 fuzz가 추가되어, 다음 회귀가 PR 단계에서 즉시 잡힌다:
|
||||
|
||||
1. **`LogSanitizer.clientIp` 마스킹 누락** — 임의의 0~255 옥텟 IPv4 주소 1만건에 대해 마지막 옥텟이 0으로 마스킹되지 않으면 즉시 실패.
|
||||
2. **매퍼의 `AUTHENTICATION_REQUIRED → 401` 변경** — 정확값 단언 + PIT가 잡음.
|
||||
3. **신규 `ClientFacingErrorCode` 추가 후 매퍼 분기 누락** — 매핑 테이블 가드 + classpath 가드 + 속성 테스트의 3중 안전망.
|
||||
4. **JSON Pointer `~`/`/` 이스케이프 누락 (RFC 6901 위반)** — 임의 입력 fuzz로 즉시 실패.
|
||||
5. **TraceIdFilter 누락 시 traceId가 JSON `null`로 직렬화되는 회귀** — sentinel `"-"` 강제.
|
||||
|
||||
## 측정 조건 (재현용)
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| BEFORE 커밋 | `c9bfdf2` (2026-05-03) |
|
||||
| AFTER 커밋 | `6f62490` (2026-05-04) — measurement run에서 build.gradle의 `**/config/**` exclusion 임시 제거 |
|
||||
| 측정 명령어 | `./gradlew clean test jacocoTestReport` |
|
||||
| PIT 측정 명령어 | `./gradlew :application:pitest :presentation:pitest` |
|
||||
| JaCoCo 버전 | 0.8.13 |
|
||||
| PIT 버전 | 1.19.1 (junit5-plugin 1.2.2) |
|
||||
| jqwik 버전 | 1.9.3 |
|
||||
|
||||
> **운영 빌드 차이점**: 운영 `build.gradle`에는 `**/config/**` exclusion이 적용되어 있어 일반 `./gradlew jacocoTestReport`는 bootstrap을 0%로 보고한다. 본 비교는 사과-사과를 위해 그 exclusion을 임시 제거한 측정값이다. exclusion 정밀화는 별도 follow-up.
|
||||
|
||||
## 한계 / 미해결
|
||||
|
||||
1. **presentation의 PIT mutation kill이 32%로 보이는 이유**는 통합 테스트가 bootstrap 모듈에 있어 PIT 측정에서 빠지기 때문이다. *커버된 영역에서의 단언 강도는 92%로 매우 높다*. 다음 사건: 멀티모듈 aggregate 리포트 도입.
|
||||
2. **bootstrap의 운영 빌드 0%**는 측정 누락이지 코드 미커버가 아니다. 다음 사건: `**/config/**` exclusion을 핸들러를 포함하지 않도록 정밀화.
|
||||
3. **Tier 1 임계치(95%+)에는 미도달.** 현재는 baseline 모드(게이트 비활성)이며, 점진적 상향 일정은 `docs/testing-coverage-policy.md`에 정의됨.
|
||||
|
||||
## 첨부 (원본 리포트)
|
||||
|
||||
- BEFORE 스냅샷: `coverage-history/v0.0.1-SNAPSHOT/2026-05-04-jqwik-before/`
|
||||
- AFTER 스냅샷: `coverage-history/v0.0.1-SNAPSHOT/2026-05-04-jqwik-after/`
|
||||
|
||||
각 스냅샷의 모듈별 진입점:
|
||||
```
|
||||
{snapshot}/application/jacoco/html/index.html # JaCoCo 라인/분기 표
|
||||
{snapshot}/application/pitest/index.html # PIT (AFTER 스냅샷에만 존재)
|
||||
{snapshot}/presentation/jacoco/html/index.html
|
||||
{snapshot}/presentation/pitest/index.html
|
||||
{snapshot}/bootstrap/jacoco/html/index.html
|
||||
{snapshot}/infrastructure/jacoco/html/index.html
|
||||
```
|
||||
|
||||
> `coverage-history/`는 git ignore 되어 로컬 전용. 같은 측정을 재현하려면
|
||||
> SUMMARY.md의 "측정 조건 (재현용)" 섹션 참고.
|
||||
@@ -0,0 +1,57 @@
|
||||
# 2026-05-04 PIT mutation 게이트 활성화
|
||||
|
||||
## 한 줄 결론
|
||||
|
||||
application/presentation 모듈에 PIT mutation 임계치 게이트를 활성화. 두 모듈 모두 baseline 측정값 이하로 떨어지면 `./gradlew :module:pitest`가 빌드 실패한다. 게이트 활성 시점 측정값: **application 94% / threshold 90%**, **presentation 77% / threshold 75%**.
|
||||
|
||||
## 변경 범위
|
||||
|
||||
`build.gradle`:
|
||||
- `:application:pitest`에 `mutationThreshold = 90`, `coverageThreshold = 90` 적용
|
||||
- `:presentation:pitest`에 `mutationThreshold = 75`, `coverageThreshold = 90` 적용
|
||||
- domain/bootstrap은 본 사건 범위 외 (domain은 wrapper만, bootstrap은 PIT 도입 후순위)
|
||||
|
||||
`docs/testing-coverage-policy.md`:
|
||||
- "PIT는 baseline만" 문구를 게이트 활성 사실로 갱신
|
||||
- 6개월 후 점진 상향 항목에서 "PIT 게이트 활성화" 제거 → "presentation 80~85%로 상향" 으로 교체
|
||||
|
||||
## 게이트 임계치 근거
|
||||
|
||||
| 모듈 | 측정값 | 임계치 | 마진 | 근거 |
|
||||
|---|---|---|---|---|
|
||||
| application | 94% (59/63 killed) | **90%** | +4%p | Tier 1 PIT 임계치(85~95%) 안전 통과. 테스트 추가/수정 시 마진 4%p 안에서 변동 허용. |
|
||||
| presentation | 77% (58/75 killed) | **75%** | +2%p | 단위 테스트 추가로 32%→77%로 점프했지만 Tier 1 목표 85%까지는 미달. 살아남은 변이 17개 분석 후 후속 PR에서 상향. |
|
||||
|
||||
## Before / After
|
||||
|
||||
PIT 점수 자체는 application/presentation 양쪽 모두 직전 측정값과 동일 (Tier 1 단위 테스트 추가 사건에서 이미 점프). 본 사건은 그 측정값을 **빌드 게이트로 강제**한다는 변화.
|
||||
|
||||
| 모듈 | Mutation Coverage | Test Strength | Game Active? |
|
||||
|---|---|---|---|
|
||||
| application | 94% | 94% | ✅ 활성 (90% threshold) |
|
||||
| presentation | 77% | 81% | ✅ 활성 (75% threshold) |
|
||||
|
||||
## 자동 검출 가능해진 회귀
|
||||
|
||||
- 누가 단언을 약화시켜 mutation kill이 임계치 아래로 떨어지는 PR → 빌드 실패
|
||||
- 테스트를 통째로 삭제/skip하는 PR → 빌드 실패
|
||||
- 새 코드를 추가하면서 mutation 대상이 늘어나 점수가 떨어지면 → 빌드 실패 (자연스러운 압력으로 작동)
|
||||
|
||||
## 측정 조건
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| 측정 명령어 | `./gradlew :application:pitest :presentation:pitest` |
|
||||
| PIT 버전 | 1.19.1 |
|
||||
| pitest-junit5-plugin | 1.2.2 |
|
||||
| 게이트 활성 검증 | 두 명령어 모두 BUILD SUCCESSFUL |
|
||||
|
||||
## 한계 / 미해결
|
||||
|
||||
- **presentation 17개 mutation 살아남음** — `ValidationExceptionHandler`의 일부 분기/메시지 폴백이 아직 단언 약함. 다음 사건: 살아남은 변이 분석 + 단언 강화로 80~85%까지.
|
||||
- **bootstrap PIT 미적용** — bootstrap은 Spring 통합 테스트가 무거워 PIT 실행 시간 큼. 별도 nightly CI에서 측정하는 정책 검토.
|
||||
- **domain PIT 미적용** — wrapper 예외만 있어 mutation 생성 안 됨. 도메인 모델이 자라면 추가.
|
||||
|
||||
## 첨부
|
||||
|
||||
- AFTER 스냅샷: `coverage-history/v0.0.1-SNAPSHOT/2026-05-04-pit-gate-active/`
|
||||
@@ -0,0 +1,71 @@
|
||||
# 2026-05-04 presentation Tier 1 95% 도달
|
||||
|
||||
## 한 줄 결론
|
||||
|
||||
`presentation.support.exception` 4개 핸들러에 단위 테스트(컨테이너 우회, 동적 프록시 + 진짜 생성자)를 추가하여 모듈 단위 측정 기준 패키지 라인 커버리지가 **24.8% → 97.9% (+73.1%p)**, Tier 1 임계치(95%) 안정적 통과. ApiErrorHttpStatusMapper / RequestExceptionHandler / ApplicationExceptionHandler는 100%.
|
||||
|
||||
## 변경 범위
|
||||
|
||||
신규 단위 테스트 4개 클래스:
|
||||
- `RequestExceptionHandlerTest` — 13개 핸들러 메서드 직접 호출 (HttpMessageNotReadable, MethodNotSupported, MissingParameter, TypeMismatch, MediaTypeNotSupported/NotAcceptable, MissingHeader, ServletRequestBinding, NoResourceFound, MaxUploadSize, ResponseStatus 4xx/5xx, ErrorResponseException)
|
||||
- `ApplicationExceptionHandlerTest` — 4개 분기 (BusinessException 매핑, MessageNotWritable committed/uncommitted, Exception 안전망)
|
||||
- `ValidationExceptionHandlerTest` — MethodArgumentNotValid(field+global), null 메시지 폴백, ConstraintViolation, HandlerMethodValidation(paramName 있음/없음)
|
||||
- `ApiErrorHttpStatusMapperDefaultBranchTest` — non-sealed default 분기 트리거
|
||||
|
||||
기존 통합 테스트는 모두 유지 — 핸들러 동작 종합 검증으로서 정책 13(다층 안전망)에 따라 같은 영역을 두 축으로 강제.
|
||||
|
||||
## Before / After 수치 (presentation 모듈 단위 측정)
|
||||
|
||||
### 패키지 `presentation.support.exception`
|
||||
|
||||
| Counter | BEFORE | AFTER | 변화 |
|
||||
|---|---|---|---|
|
||||
| LINE | 24.8% | **97.9%** | +73.1%p |
|
||||
| BRANCH | 42.3% | **85.5%** | +43.2%p |
|
||||
|
||||
### 클래스별 (AFTER)
|
||||
|
||||
| 클래스 | LINE | BRANCH |
|
||||
|---|---|---|
|
||||
| `ApiErrorHttpStatusMapper` | 100.0% | 100.0% |
|
||||
| `RequestExceptionHandler` | 100.0% | 100.0% |
|
||||
| `ApplicationExceptionHandler` | 100.0% | 100.0% |
|
||||
| `PresentationErrorCode` | 100.0% | (no branch) |
|
||||
| `ValidationExceptionHandler` | 94.2% (6 miss) | 80.4% (11 miss) |
|
||||
|
||||
### presentation 모듈 전체
|
||||
|
||||
| Counter | BEFORE | AFTER |
|
||||
|---|---|---|
|
||||
| LINE | 24.8% | **90.4%** |
|
||||
| BRANCH | 42.3% | 83.3% |
|
||||
| METHOD | 25.0% | 84.6% |
|
||||
|
||||
## 자동 검출 가능해진 회귀
|
||||
|
||||
- 핸들러 한 메서드를 깨면 `presentation:test` 단계에서 즉시 실패 — 통합 테스트가 다른 모듈에 있어도 무관하게 검증
|
||||
- jacocoAggregateReport 없이도 운영 빌드의 모듈별 게이트만으로 회귀 차단 가능
|
||||
|
||||
## 구현 메모
|
||||
|
||||
- Mockito 의존성 도입 없이 **JDK 동적 프록시**로 HttpServletRequest/Response stub 작성
|
||||
- `ParameterValidationResult`는 클래스이므로 동적 프록시 불가 → 7-arg 생성자를 그대로 사용
|
||||
- 핸들러 메서드는 패키지-private이라 같은 패키지 테스트가 직접 호출 가능
|
||||
|
||||
## 측정 조건
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| BEFORE 커밋 | `e3693af` (aggregate 도입 직후) |
|
||||
| AFTER 커밋 | (본 커밋) |
|
||||
| 측정 명령어 | `./gradlew clean test jacocoTestReport` |
|
||||
| 비교 대상 | presentation 모듈 단위 측정 (모듈 격리 측정 — 통합 테스트 제외) |
|
||||
|
||||
## 한계 / 미해결
|
||||
|
||||
- `ValidationExceptionHandler`의 BRANCH 80.4% — 11줄 미커버는 주로 `HandlerMethodValidationException`의 부수 분기와 메시지 폴백의 일부. 다음 작업에서 추가 케이스로 95% 도달 가능.
|
||||
- application Tier 1 패키지(`application/support/exception`, `application/support/audit`)는 아직 단위 테스트 부족 — 다음 사건(3번)에서 처리.
|
||||
|
||||
## 첨부
|
||||
|
||||
- AFTER 스냅샷: `coverage-history/v0.0.1-SNAPSHOT/2026-05-04-presentation-tier1/`
|
||||
@@ -0,0 +1,124 @@
|
||||
# Testing History
|
||||
|
||||
이 폴더는 테스트 인프라 변경의 사건 단위 비교 기록이다. 정책(임계치, Tier 분류)은 [`../testing-coverage-policy.md`](../testing-coverage-policy.md)를 참조한다.
|
||||
|
||||
## 폴더 구조
|
||||
|
||||
```
|
||||
docs/testing-history/
|
||||
├── README.md # 본 문서
|
||||
└── {YYYY-MM-DD}-{사건명}/ # 사건 단위 폴더 (영구 보존, git)
|
||||
└── SUMMARY.md # 한 페이지 결론 (수치 + 의미 + 한계)
|
||||
```
|
||||
|
||||
원본 JaCoCo/PIT 스냅샷은 다음 위치에 **버전·날짜 단위로 누적 보관**한다 (git ignore, 로컬 전용):
|
||||
|
||||
```
|
||||
coverage-history/
|
||||
└── v{project.version}/ # 예: v0.0.1-SNAPSHOT
|
||||
└── {YYYY-MM-DD}-{사건명}/ # 같은 버전 안에서 누적
|
||||
├── application/{jacoco,pitest}/
|
||||
├── presentation/{jacoco,pitest}/
|
||||
├── infrastructure/jacoco/
|
||||
├── bootstrap/jacoco/
|
||||
└── summary.txt
|
||||
```
|
||||
|
||||
> 같은 버전 안에서는 사건이 누적된다 (`v0.0.1-SNAPSHOT/2026-05-04-jqwik-before`, `v0.0.1-SNAPSHOT/2026-05-04-jqwik-after`, `v0.0.1-SNAPSHOT/2026-05-20-aggregate-after` ...). 릴리스 버전이 올라가면 자동으로 새 디렉토리에 적재되어 버전 단위 비교도 가능하다.
|
||||
|
||||
## 라벨 명명 규칙 (강제)
|
||||
|
||||
- 형식: `{YYYY-MM-DD}-{kebab-case 사건명}`
|
||||
- 정규식: `^\d{4}-\d{2}-\d{2}-[a-z0-9][a-z0-9-]*$`
|
||||
- 예: `2026-05-04-jqwik-before`, `2026-05-04-jqwik-after`, `2026-08-12-aggregate-before`
|
||||
- `archiveCoverageReport` 태스크가 형식 검증 + 같은 라벨 중복 차단
|
||||
|
||||
## 새 사건 기록 절차 (체크리스트)
|
||||
|
||||
1. **BEFORE 스냅샷 측정**
|
||||
```bash
|
||||
git checkout {pre-change-commit}
|
||||
./gradlew clean test jacocoTestReport :application:pitest :presentation:pitest
|
||||
./gradlew archiveCoverageReport -Plabel=YYYY-MM-DD-사건명-before
|
||||
```
|
||||
2. **변경 적용 후 AFTER 스냅샷 측정**
|
||||
```bash
|
||||
git checkout {feature-branch}
|
||||
./gradlew clean test jacocoTestReport :application:pitest :presentation:pitest
|
||||
./gradlew archiveCoverageReport -Plabel=YYYY-MM-DD-사건명-after
|
||||
```
|
||||
3. `docs/testing-history/{YYYY-MM-DD}-{사건명}/SUMMARY.md` 작성 (아래 템플릿)
|
||||
4. 커밋 — `coverage-history/`는 ignore되므로 SUMMARY.md만 git에 들어간다
|
||||
|
||||
> **중요**: 측정 조건은 BEFORE/AFTER가 같아야 한다. 같은 명령어, 같은 도구 버전, 같은 exclusion. 사과-사과 비교가 아니면 수치를 비교할 수 없다.
|
||||
|
||||
## 어떻게 보나
|
||||
|
||||
| 보고 싶은 것 | 어디로 |
|
||||
|---|---|
|
||||
| 한 페이지 결론과 변화량 | `docs/testing-history/{사건명}/SUMMARY.md` |
|
||||
| 모듈별 라인/분기 % | `coverage-history/v{ver}/{label}/{module}/jacoco/html/index.html` |
|
||||
| PIT 단언 강도 | `coverage-history/v{ver}/{label}/{module}/pitest/index.html` |
|
||||
| 정책(임계치/Tier) | `../testing-coverage-policy.md` |
|
||||
|
||||
## SUMMARY.md 템플릿
|
||||
|
||||
```markdown
|
||||
# YYYY-MM-DD 사건명
|
||||
|
||||
## 한 줄 결론
|
||||
무엇을 바꿔서 어떤 수치가 어떻게 변했는지 한 문장.
|
||||
|
||||
## 변경 범위
|
||||
- 의존성/도구
|
||||
- 추가/수정된 테스트
|
||||
- 영향 모듈
|
||||
|
||||
## Before / After 수치 (사과-사과)
|
||||
| 모듈 | BEFORE | AFTER | 변화 |
|
||||
| ... |
|
||||
|
||||
## 자동 검출 가능해진 회귀 시나리오
|
||||
1. 구체적 시나리오 1
|
||||
2. ...
|
||||
|
||||
## 측정 조건 (재현용)
|
||||
- BEFORE 커밋 / AFTER 커밋
|
||||
- 측정 명령어
|
||||
- 도구 버전
|
||||
- 같은 exclusion 여부
|
||||
|
||||
## PIT (단언 강도)
|
||||
| 모듈 | Mutations | Killed | Test Strength |
|
||||
| ... |
|
||||
|
||||
## 한계 / 미해결
|
||||
- 다음 사건의 입력이 될 항목들
|
||||
|
||||
## 첨부 (원본 스냅샷)
|
||||
- coverage-history/v{ver}/{YYYY-MM-DD}-사건명-before/
|
||||
- coverage-history/v{ver}/{YYYY-MM-DD}-사건명-after/
|
||||
```
|
||||
|
||||
## 보존 정책
|
||||
|
||||
- `docs/testing-history/{사건명}/SUMMARY.md` — **영구 보존** (git)
|
||||
- `coverage-history/v{ver}/{label}/` 원본 스냅샷 — **로컬 전용**, 누적 보관, 필요 시 재생성 가능
|
||||
- 진급/감사 등 외부 제출이 필요하면 그때 별도 zip으로 export
|
||||
- 스크린샷은 보관하지 않는다 — HTML 리포트가 원본이다. 외부 제출용은 그때 생성 후 폐기.
|
||||
|
||||
## 명령어 치트시트
|
||||
|
||||
```bash
|
||||
# 일반 측정 (게이트 없음, 리포트만)
|
||||
./gradlew clean test jacocoTestReport
|
||||
|
||||
# PIT까지 측정 (도구 도입 사건일 때)
|
||||
./gradlew :application:pitest :presentation:pitest
|
||||
|
||||
# 스냅샷 archive (버전·라벨 필수)
|
||||
./gradlew archiveCoverageReport -Plabel=2026-05-04-jqwik-after
|
||||
|
||||
# 게이트 검증 (CI에서 PR 단위)
|
||||
./gradlew coverageGate
|
||||
```
|
||||
Reference in New Issue
Block a user