Skip to content

chore: 업그레이드 후속 정리 및 테스트 타입 체크 도입 - #49

Merged
kyj0503 merged 10 commits into
mainfrom
chore/post-upgrade-cleanup
Aug 2, 2026
Merged

chore: 업그레이드 후속 정리 및 테스트 타입 체크 도입#49
kyj0503 merged 10 commits into
mainfrom
chore/post-upgrade-cleanup

Conversation

@kyj0503

@kyj0503 kyj0503 commented Aug 1, 2026

Copy link
Copy Markdown
Owner

#47·#48 병합 후 코드 리뷰에서 발견한 문제 5건을 정리합니다. 기능 변경은 없습니다.

배경

리뷰 중 "자동 검증은 통과하는데 실제로는 깨져 있거나 죽어 있는" 것들이 반복해서 나왔습니다. 개별 수정과 함께, 그 부류가 다시 쌓이지 않도록 하는 장치(테스트 타입 체크)를 같이 넣습니다.

변경 내용

커밋 내용
bff966e compose.dev-prod.yaml에 남아 있던 API 경로 이중 접두사 설정 정리
c178a59 8개월간 깨져 있던 DCA 계산 테스트를 현재 API로 복구
b50d658 df9b66c에서 놓친 죽은 코드 및 stale 설정 정리
c0ecbce opt-in 전환 이후 방치된 ThemeSelector 테스트 복구
2ab58fb 테스트 파일 타입 체크 도입 (tsconfig.test.json)

1. dev-prod compose 설정 (bff966e)

042dc34compose.dev.yaml만 고치고 형제 파일을 놓쳐, dev-prod는 VITE_API_BASE_URL=/api가 그대로였습니다. 현재는 client.ts의 인터셉터가 막아주지만 그것은 설정 오류에 대한 방어막이지 정상 경로가 아닙니다. dev-prod만 상시 방어막에 의존해 도는 상태였습니다.

2. stale 테스트 2건 (c178a59, c0ecbce)

둘 다 프로덕션은 정상이고 테스트가 낡은 경우입니다.

  • DCA 테스트: d00730c(2025-11-11)에서 getDcaWeeks가 삭제되고 DcaFrequency 유니온이 바뀌었는데 테스트가 옛 API 그대로였습니다. 주목할 점은, weekly_4를 쓰던 테스트들이 그동안 통과하고 있었던 이유가 getDcaPeriodInfo가 미등록 빈도를 monthly_1으로 조용히 폴백하기 때문이라는 것입니다. 즉 검증하는 바 없이 우연히 같은 답을 계산하고 있었습니다.
  • ThemeSelector: f0ba94a(2025-11-11)에서 토글이 showDarkModeToggle prop(기본 false)으로 감싸졌는데 테스트가 prop 없이 렌더해, 버튼이 아예 마운트되지 않고 있었습니다.

3. 죽은 코드 정리 (b50d658)

df9b66c의 정리에서 놓친 것들입니다. 특히 src/shared/config/index.ts는 단순 미사용을 넘어 유해했습니다 — 이 파일의 buildApiUrl은 기본값이 ${origin}/apibase.ts/client.ts가 세운 계약과 정반대이고, WS_BASE_URL은 존재하지 않는 WebSocket 엔드포인트를 가리킵니다.

4. 테스트 타입 체크 (2ab58fb) — 재발 방지

위 2번이 8개월간 발견되지 않은 근본 원인입니다. type-checktsconfig.build.json 기준인데 이 설정이 테스트 파일을 제외하고, vitest는 타입 체크를 하지 않습니다. 즉 삭제된 함수를 import해도 컴파일에서 안 잡힙니다.

tsconfig.test.json + type-check:test를 추가하고, 그로 인해 드러난 타입 오류 41건을 수정했습니다. strict 옵션은 하나도 낮추지 않았고 any/ts-ignore도 쓰지 않았습니다.

동작 확인:

# 삭제된 함수를 import 하도록 일부러 되돌린 상태
$ npm run type-check:test
error TS2305: Module '"../../model/strategyConfig"' has no exported member 'getDcaWeeks'.
EXIT=2

$ npm run type-check     # 기존 빌드용 설정
EXIT=0                   # ← 메우려던 구멍이 실재했음

부수적으로 타입 오류를 제대로 고치는 과정에서 테스트가 실제로 강해진 곳이 있습니다. backtestFormReducer/useBacktestForm 테스트는 portfolio[0] 순서를 암묵적으로 가정하고 있었는데, 이제 길이와 symbol을 먼저 검증합니다. 또 UnifiedInfoSection 테스트의 mock에서 VolatilityEvent.volume 누락(이미 발생해 있던 drift)이 발견됐습니다.

검증

컨테이너에서 실측했습니다.

항목 before after
FE 테스트 109 passed / 4 failed 113 passed / 0 failed
type-check 0 errors 0 errors
type-check:test (존재하지 않음) 0 errors
eslint 5 problems (2 errors / 3 warnings) 3 problems (0 errors / 3 warnings)
build 성공 성공
BE pytest tests/unit 141 passed 141 passed (BE 무변경)

남은 warning 3건은 exhaustive-deps로, 의존성을 강제로 넣으면 런타임 동작이 바뀔 수 있는 훅이라 의도적으로 둡니다.

후속 과제 (이번 범위 밖)

  • recalcAmountsByWeight.test.tsbacktestFormReducer.ts의 module-private 함수를 손으로 복사해 두고 그 복사본을 테스트합니다. 원본과 조용히 갈라질 수 있어, 실제 함수를 export하고 복사본을 지우는 편이 좋습니다.
  • getDcaPeriodInfo가 미등록 빈도를 monthly_1으로 조용히 폴백합니다. 타입상 닫힌 유니온이라 컴파일 시점엔 안전하지만, d00730c 이전에 저장된 포트폴리오처럼 타입을 우회해 들어온 값(예: weekly_8)은 경고 없이 잘못된 DCA 총액을 만듭니다.
  • Jenkinsfile에 lint·테스트 게이트가 없습니다. 이 PR로 lint 에러 0 / 테스트 전건 통과가 됐으므로 지금이 게이트를 걸기 좋은 시점입니다.

🤖 Generated with Claude Code


추가 (2026-08-02): CI 게이트 및 그 과정에서 드러난 문제 2건

CI에 lint·테스트 게이트를 붙이려다, 게이트가 붙기도 전에 문제 두 건을 잡아냈습니다.

커밋 내용
efd5960 isolate:false로 인한 flaky 테스트 스위트 수정
7fa1154 로컬 빌드가 개발용 React 번들을 내던 문제 수정
3c80c84 CI에 lint·타입체크·테스트 게이트 추가

1. 테스트 스위트가 flaky했습니다 (efd5960) — 가장 중요

같은 커밋에서 npm run test:run을 반복하면 결과가 뒤집힙니다.

run#1: 113 passed
run#2: 3 failed | 110 passed
run#3: 113 passed
run#4: 3 failed | 110 passed

깨끗한 도커 빌드에서는 최대 9건까지 실패했습니다(routing 5, ThemeSelector 3, backtestService.integration 1).

원인은 vitest.config.tsisolate: false입니다. 모든 테스트 파일이 하나의 happy-dom 환경을 공유하는데, vitest는 직전 실행의 파일별 소요시간을 캐시해 실행 순서를 조정합니다. 순서가 바뀌면 오염 양상이 달라지고 통과 여부가 뒤집힙니다. 개별 파일만 돌리면 항상 통과하므로 테스트나 프로덕션 코드의 결함은 아닙니다.

isolate를 vitest 기본값 true로 되돌렸습니다. dev 컨테이너 5회 연속 113 passed, 깨끗한 빌드에서도 113 passed로 편차가 사라졌습니다.

이 PR의 앞선 커밋들에 적힌 "113 전건 통과" 검증 결과는 운이었습니다. 같은 명령이 실행마다 다른 답을 냈으므로 증거로서 가치가 없었습니다. 이 커밋 이후의 수치만 신뢰할 수 있습니다.

2. 로컬 빌드가 개발용 React 번들을 내고 있었습니다 (7fa1154)

로컬:    2511 modules, react-vendor 422.18 kB, CSS 73.42 kB
Jenkins: 2506 modules, react-vendor 229.94 kB, CSS 70.78 kB

Dockerfile.devENV NODE_ENV=developmentdocker compose exec으로 실행한 빌드까지 새어 들어가, vite가 React 개발 빌드를 번들에 포함시키고 있었습니다. Jenkins는 NODE_ENV가 없어 정상이었으므로 배포물에는 영향이 없습니다. 문제는 로컬 검증이 CI와 다른 산출물을 대상으로 이뤄졌다는 점입니다.

build 스크립트에 NODE_ENV=production을 명시했습니다. 이제 dev 컨테이너에서 빌드해도 Jenkins와 청크 해시까지 동일합니다(react-vendor-BsCdM4_4, chart-vendor-DwUT2tqu).

3. CI 게이트 (3c80c84)

Jenkins 에이전트에 node/python이 있다고 가정하지 않도록, 각 Dockerfile에 test 스테이지를 두고 CI가 --target test로 호출합니다.

  • FE: deps → test / build → nginx. test는 lint / type-check / type-check:test / vitest를 각각 별도 RUN으로 실행.
  • BE: base → test / runtime. DB가 필요 없는 pytest tests/unit.

test 스테이지 모두 최종 이미지의 의존 경로에 없어 기존 빌드 동작과 산출물은 그대로입니다(BE runtime 이미지에 tests 미포함 확인). deps/base 레이어는 뒤이은 이미지 빌드가 재사용하므로 의존성 설치가 두 번 돌지 않습니다.

Jenkinsfile에는 Login GHCR 앞에 Quality Gate 스테이지를 두고 FE/BE를 parallel로 돌립니다.

차단 동작을 확인했습니다 — 일부러 실패하는 테스트를 주입하면 FE/BE 게이트 모두 exit 1로 빌드가 중단되고, 이미지 빌드·배포에 도달하지 못합니다.

검토해 주실 판단 하나: lint 상한 0 → 3

npm run lint--max-warnings 0이라 exhaustive-deps 경고 3건 때문에 현재 어떤 경우에도 통과할 수 없는 상태였습니다. 통과 불가능한 게이트는 무의미하고, 그렇다고 동작이 바뀔 수 있는 훅 수정을 이 커밋에 섞는 것은 더 나쁘다고 판단해 현재 개수를 상한으로 고정하는 래칫으로 바꿨습니다. 에러 0은 그대로 강제되고 경고가 늘어나는 것은 막힙니다.

남은 3건이며, 상한을 0까지 내리는 것이 목표입니다:

src/features/backtest/hooks/useStrategyParams.ts:55, :90
src/shared/hooks/useAsync.ts:85

기존 설정을 완화하는 변경이라 명시적으로 짚어 둡니다 — 그대로 두는 편이 낫다고 보시면 되돌리겠습니다.

부수 변경

FE에 .dockerignore가 아예 없어 node_modules, dist, .git이 빌드 컨텍스트로 들어가고 있었습니다. Jenkins는 매번 새로 clone하므로 드러나지 않았지만 로컬 빌드에서는 호스트 산출물이 섞일 수 있어 추가했습니다.

게이트의 보장 범위 (의도된 설계)

Jenkinsfile*/main을 명시적으로 체크아웃하고, 이 저장소는 GitHub 체크·브랜치 보호를 쓰지 않습니다. 따라서 이 게이트는 배포 게이트이지 병합 게이트가 아닙니다.

  • 막는 것: 깨진 코드가 GHCR에 푸시되고 운영(backtest.yeonjae.kr)에 배포되는 것
  • 막지 않는 것: 깨진 코드가 main에 병합되는 것 자체

개인 프로젝트이고 롤백이 자유로우므로 의도적으로 이 범위를 택했습니다. 병합까지 막으려면 GitHub Actions PR 워크플로와 브랜치 보호가 필요합니다.

build:analyze 후속 (a5e7e09 참고)

NODE_ENV=production을 맞춰 개발 빌드를 분석하던 문제는 해소했지만, 이 스크립트는 현재 build와 동일한 일을 합니다 — 번들 분석 플러그인도 .env.analyze도 없고 vite.config.ts에서 modesourcemap 판정에만 쓰입니다(analyze/production 모두 false). README.md:205는 이를 "번들 크기 분석"으로 안내합니다. 분석 도구를 붙이거나 스크립트와 README를 함께 정리하는 판단이 남아 있습니다.

kyj0503 and others added 5 commits August 1, 2026 23:19
042dc34에서 compose.dev.yaml의 VITE_API_BASE_URL 기본값을 빈 값으로
고쳤으나 형제 파일인 compose.dev-prod.yaml은 그대로 `:-/api`였다.

서비스 레이어가 axios에 전체 경로(/api/v1/...)를 넘기는 계약이므로
여기에 /api가 들어가면 /api/api/v1/backtest가 된다. 현재는 client.ts의
인터셉터가 막아주지만 그것은 설정 오류에 대한 방어막이지 정상 경로가
아니다. dev-prod만 상시 방어막에 의존해 도는 상태였다.

compose.dev.yaml과 동일하게 `${VITE_API_BASE_URL-}`로 맞춘다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
d00730c(2025-11-11, DCA 주기 계산 로직 개선 및 레거시 함수 제거)에서
getDcaWeeks가 삭제되고 DcaFrequency 유니온이 weekly_1|weekly_2|
monthly_1|2|3|6|12로 바뀌었으나 테스트가 옛 API 그대로 남아 있었다.

수정 내용:
- getDcaWeeks describe 블록 → calculateDcaPeriods 테스트로 대체.
  삭제된 함수의 의도(빈도 → 주기 매핑 검증)에 대응하는 현재 함수이며
  그동안 테스트가 없었다. 현재 7개 빈도를 모두 커버한다.
- weekly_4/8/12 → monthly_1/2/3. 기대값은 구현
  (floor(기간일수 / 주기일수) + 1, weekly=7일 / monthly=30일 근사)에서
  역산했다. 279일 기준 monthly_1=10회, monthly_2=5회, monthly_3=4회로
  기존 테스트가 의도했던 10/5/4회 시나리오가 그대로 보존된다.
- 산술을 설명하는 주석을 실제 일수 기준으로 갱신했다.

주의할 점: weekly_4를 쓰던 기존 테스트들이 "통과"하고 있었던 것은
getDcaPeriodInfo가 미등록 빈도에 대해 monthly_1(30일)로 조용히
폴백하기 때문이었다. 즉 monthly_1의 답을 우연히 계산하고 있었을 뿐
검증하는 바가 없었다. weekly_8/12가 실패한 것도 같은 폴백 탓이다.

프로덕션 코드는 건드리지 않았다.

검증: test:run 113 tests, portfolioCalculations 12건 전부 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1. src/shared/config/index.ts 삭제 (소비자 0개)
   grep으로 shared/config, AppConfig, WS_BASE_URL 모두 파일 자신 외
   참조가 없음을 확인했다. 단순히 미사용인 것을 넘어 유해했다.
   이 파일의 buildApiUrl은 기본값이 `${origin}/api`로 base.ts/client.ts가
   세운 계약(빈 문자열)과 정반대이고, WS_BASE_URL은 이 프로젝트에
   존재하지 않는 WebSocket 엔드포인트를 가리킨다. 다음 사람이 이걸
   실제 설정으로 오해할 여지가 있었다.

2. base.ts의 export된 buildApiUrl 제거 (호출자 0개)
   유일한 참조가 위에서 삭제한 죽은 모듈의 동명 지역 함수였다.
   client.ts가 쓰는 getApiBaseUrl은 유지한다.

3. backtest_fe/__tests__/recalcAmountsByWeight.test.ts를
   src/features/backtest/model/__tests__/로 이동
   src 밖에 있던 유일한 테스트 파일이다. tsconfig의 include가 "src"라
   그동안 타입 체크를 전혀 받지 않고 vitest 실행만 되고 있었다.
   이동으로 처음 타입 체크 범위에 들어온다.
   함께 남아 있던 no-explicit-any 2건도 실제 타입(DcaFrequency, Stock)으로
   교체했다. Stock.weight가 optional이라 reducer 원본과 동일하게
   기본값 0으로 처리했다(수집 조건상 도달 불가, 동작 변화 없음).

4. vite.config.ts의 /api/v1/naver-news 프록시 규칙 제거
   뉴스 기능 코드는 df9b66c에서 전부 삭제됐고 src에 참조가 없다.

검증: type-check 0 errors, eslint 3 problems(0 errors, 3 warnings) —
lint 에러 2건 → 0, build 성공, test:run 113 tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
f0ba94a(2025-11-11, "다크 모드 토글 비활성화")에서 토글이
showDarkModeToggle prop(기본값 false)으로 감싸졌으나 테스트는
<ThemeSelector />를 prop 없이 렌더한 채 남아 있었다. 즉 토글 버튼이
아예 마운트되지 않았고, getByRole이 이름 불일치가 아니라 대상 부재로
실패하고 있었다. 그날 이후 계속 실패 상태였다.

프로덕션은 정상이다. 유일한 소비자인 Header.tsx도
showDarkModeToggle={false}를 명시하고 있어 opt-in이 의도된 설계다.
Tailwind 4 마이그레이션(38d6a8e)과는 무관하다 — 다크 모드는
useTheme의 classList 기반이라 영향을 받지 않는다.

테스트를 showDarkModeToggle을 켜서 렌더하도록 고치고, 클릭 시
toggleDarkMode가 1회 호출되는지 검증하는 원래 의도는 유지했다.
추가로 기본 렌더에서는 토글이 없음을 먼저 확인하도록 보강해
opt-in 계약이 양방향으로 고정되게 했다.

검증: test:run 113 passed / 0 failed (16 files).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## 배경

테스트 파일은 그동안 타입 체크를 전혀 받지 않았다. type-check는
tsconfig.build.json 기준인데 이 설정이 *.test.ts(x)와 __tests__/를
제외하고, vitest는 타입 체크를 하지 않는다.

그 결과 삭제된 함수를 import하는 테스트가 8개월간 방치됐다
(c178a59 참고). 런타임 실패로만 드러나므로 발견이 늦는다.

## 변경

- tsconfig.test.json 추가. build 설정이 제외하는 것(테스트 파일,
  src/test/**, vite/vitest config)을 정확히 반대로 포함한다.
  globals: true에 맞춰 types에 vitest/globals, node,
  @testing-library/jest-dom를 지정하고, types를 좁히면 vite/client가
  빠지므로 src/vite-env.d.ts를 명시적으로 포함한다.
  references: []로 tsconfig.node.json 프로젝트 참조를 끊는다.
- npm script type-check:test 추가. 기존 type-check는 그대로 둔다.
- 이로써 드러난 타입 오류 41건 수정. strict 옵션은 하나도 낮추지
  않았고 any/ts-ignore/ts-expect-error도 쓰지 않았다.

## 오류 유형별 수정

- Record<string, unknown> 제약 위반 10건 (useForm.test.ts):
  interface는 암묵적 인덱스 시그니처를 못 받으므로 지역 타입을
  interface → type으로 바꿨다. 프로덕션 제약은 정당하므로 유지.
- 배열 인덱싱 possibly undefined 21건: `!` 대신 assert.isDefined와
  길이/식별자 검증을 앞에 두는 방식으로 고쳤다. 타입을 좁히면서
  런타임 검증도 늘어난다.
- 구조가 어긋난 mock 6건 (UnifiedInfoSection.test.tsx):
  VolatilityEvent의 필수 필드 volume이 빠져 있었다. 이미 발생해
  있던 drift다. satisfies로 고정해 이후 타입 변경 시 컴파일에서 깨지게 했다.
- MSW 요청 바디 무타입 1건: http.post<never, BacktestRequest>로
  소스에서 타입을 주도록 바꿨다.
- toISOString().split('T')[0] 1건 → slice(0, 10).

## 부수 효과: 테스트가 실제로 강해진 곳

- backtestFormReducer / useBacktestForm: portfolio[0] 순서를 암묵적으로
  가정하던 단언이 이제 길이와 symbol을 먼저 검증한다. 리듀서가 항목을
  재배열하거나 누락시키면 명확한 메시지로 실패한다.
- recalcAmountsByWeight: 비중 재계산이 포트폴리오 형태를 보존하는지
  검증하는 단언이 없었는데 추가됐다.

## 검증

type-check:test 0 errors, type-check 0 errors,
eslint 3 problems(0 errors, 3 warnings), test:run 113 passed, build 성공.

동작 확인: 삭제된 함수를 import하도록 일부러 되돌려 보면
type-check:test가 TS2305로 실패하고(EXIT=2), 같은 상태에서 기존
type-check는 EXIT=0으로 통과한다. 메우려던 구멍이 실재했음이 확인된다.

## 남은 과제 (이번 범위 밖)

recalcAmountsByWeight.test.ts는 backtestFormReducer.ts 안의
module-private 함수를 손으로 복사해 두고 그 복사본을 테스트한다.
원본과 조용히 갈라질 수 있다. 실제 함수를 export하고 복사본을
지우는 후속 작업이 필요하다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 1, 2026 14:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

의존성 업그레이드 후속(#47·#48)으로 남아 있던 설정 불일치/죽은 코드/낡은 테스트를 정리하고, 테스트 파일도 TypeScript 타입체크에 포함되도록 별도 tsconfig.test.json과 스크립트를 추가해 “자동 검증은 통과하지만 실제로는 깨진 상태”가 재발하지 않게 하는 정리 PR입니다.

Changes:

  • dev-prod compose에서 VITE_API_BASE_URL 기본값 처리(빈 값 유지)로 API 경로 이중 접두사 문제를 제거
  • stale 테스트(DCA/ThemeSelector 등) 및 테스트 mock 타입 정확도 개선
  • 테스트 전용 tsconfig.test.json + type-check:test 스크립트 도입, 관련 타입 오류 정리

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
compose.dev-prod.yaml dev-prod 환경에서 VITE_API_BASE_URL 기본값을 빈 문자열 유지로 변경해 /api/api/... 요청 방지
backtest_fe/vite.config.ts 더 이상 쓰지 않는 프록시 엔드포인트(/api/v1/naver-news) 제거
backtest_fe/tsconfig.test.json 테스트 파일 포함 타입체크를 위한 TS 설정 신규 추가
backtest_fe/src/shared/hooks/tests/useForm.test.ts useForm 제네릭 제약에 맞춰 테스트 타입 정의 정리
backtest_fe/src/shared/config/index.ts 구(舊) 설정 유틸/값 제거(죽은 코드 정리)
backtest_fe/src/shared/components/layout/tests/ThemeSelector.test.tsx opt-in prop(showDarkModeToggle) 반영해 토글 테스트 복구
backtest_fe/src/shared/api/base.ts 사용되지 않는 URL 빌더 제거 및 계약(빈 base URL) 유지
backtest_fe/src/lib/tests/chartUtils.test.ts 날짜 포맷 테스트 구현 세부 수정(동일 결과, 더 명확한 구현)
backtest_fe/src/features/backtest/utils/tests/portfolioCalculations.test.ts DCA 계산 테스트를 현재 주기 계산 로직(calculateDcaPeriods) 기준으로 복구/강화
backtest_fe/src/features/backtest/services/tests/backtestService.integration.test.ts MSW 핸들러의 요청 바디 타입을 명시해 타입 안정성 개선
backtest_fe/src/features/backtest/model/tests/recalcAmountsByWeight.test.ts 테스트 내 복사 로직 타입 강화 및 검증(항목 유실 방지 등)
backtest_fe/src/features/backtest/model/tests/backtestFormReducer.test.ts 포트폴리오 항목을 심볼 기반으로 검증하도록 테스트 강화
backtest_fe/src/features/backtest/hooks/tests/useBacktestForm.test.ts 포트폴리오 상태 검증을 더 명시적으로 강화
backtest_fe/src/features/backtest/components/results/tests/UnifiedInfoSection.test.tsx mock 데이터를 실제 타입(satisfies)에 맞게 보강(필드 drift 방지)
backtest_fe/package.json 테스트 타입체크 스크립트 type-check:test 추가
Suppressed comments (1)

backtest_fe/src/features/backtest/model/tests/recalcAmountsByWeight.test.ts:2

  • DcaFrequencydcaConfig.ts에서 export type로만 내보내므로 값 import가 아니라 type-only import로 가져오는 게 맞습니다. 현재처럼 값 import로 쓰면 설정에 따라(verbatimModuleSyntax 등) 타입체크가 실패하거나, 리뷰 시 혼동을 줄 수 있습니다.

Comment on lines +9 to +11
// `useForm<T>` requires `T extends Record<string, unknown>`.
// A type alias of an object literal gets an implicit index signature; an interface does not.
type TestFormData = {
kyj0503 and others added 5 commits August 2, 2026 10:27
같은 커밋에서 npm run test:run을 반복하면 113 passed와 3 failed가
번갈아 나온다. 4회 연속 실행 결과: 113 / 110 / 113 / 110.
깨끗한 도커 빌드에서는 최대 9건까지 실패했다
(routing 5, ThemeSelector 3, backtestService.integration 1).

원인은 vitest.config.ts의 isolate: false다. 모든 테스트 파일이 하나의
happy-dom 환경을 공유하는데, vitest는 직전 실행의 파일별 소요시간을
캐시해 실행 순서를 조정한다. 순서가 실행마다 바뀌면서 오염 양상이
달라지고, 그 결과 통과 여부가 뒤집힌다.

개별 파일만 돌리면 항상 통과하므로(routing 5 passed) 테스트 자체나
프로덕션 코드의 문제가 아니다. 전체 스위트를 공유 환경에서 돌릴 때만
드러난다.

isolate를 vitest 기본값인 true로 되돌린다. 격리 비용이 붙지만
결과를 신뢰할 수 있는 편이 우선이다.

검증: dev 컨테이너에서 5회 연속 113 passed (편차 없음).
깨끗한 도커 빌드에서도 113 passed.

주의: 이 커밋 이전의 "113 전건 통과" 검증 결과들은 운이었다.
같은 명령이 실행마다 다른 답을 냈으므로 증거로서 가치가 없었다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dev 컨테이너에서 npm run build를 돌리면 Jenkins와 산출물이 달랐다.

  로컬:    2511 modules, react-vendor 422.18 kB, CSS 73.42 kB
  Jenkins: 2506 modules, react-vendor 229.94 kB, CSS 70.78 kB

원인은 Dockerfile.dev의 ENV NODE_ENV=development다. dev 서버에는
맞는 설정이지만 docker compose exec으로 실행한 빌드까지 새어 들어가,
vite가 이미 설정된 NODE_ENV를 존중해 React 개발 빌드를 번들에
포함시키고 있었다. Jenkins는 NODE_ENV가 없어 정상 동작했으므로
배포물에는 영향이 없었다.

문제는 로컬 빌드 검증이 CI와 다른 산출물을 대상으로 이뤄졌다는 점이다.
build 스크립트에서 NODE_ENV=production을 명시해 주변 환경과 무관하게
결정적으로 동작하게 한다.

검증: NODE_ENV=development인 dev 컨테이너에서 빌드해도
2506 modules / react-vendor 229.94 kB로, Jenkins 빌드와 청크 해시까지
동일하다(react-vendor-BsCdM4_4, chart-vendor-DwUT2tqu).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
지금까지 Jenkins는 빌드/푸시/배포와 헬스체크만 했다. 테스트가 깨져도
배포됐고, 실제로 8개월간 깨진 테스트가 배포를 막지 못했다.
lint 에러 0 / 테스트 전건 통과가 된 지금이 게이트를 걸 시점이다.

## 방식

Jenkins 에이전트에 node/python이 있다고 가정하지 않도록, 각 Dockerfile에
test 스테이지를 두고 CI가 --target test로 호출한다.

- backtest_fe/Dockerfile: deps → test / build → nginx로 분리.
  test는 lint, type-check, type-check:test, vitest를 각각 별도 RUN으로
  돌려 어디서 깨졌는지 로그에서 바로 보이게 한다.
- backtest_be_fast/Dockerfile: base → test / runtime으로 분리.
  DB가 필요 없는 pytest tests/unit만 돌린다.

두 test 스테이지 모두 최종 이미지의 의존 경로에 없다. 따라서
docker build(타깃 미지정)로는 실행되지 않아 기존 빌드 동작과 산출물이
그대로다. BE runtime 이미지에 tests가 포함되지 않는 것도 확인했다.
deps/base 레이어는 뒤이은 이미지 빌드가 재사용하므로 의존성 설치가
두 번 돌지 않는다.

Jenkinsfile에는 Login GHCR 앞에 Quality Gate 스테이지를 두고 FE/BE를
parallel로 돌린다. 실패하면 이미지 빌드와 배포에 도달하지 못한다.

## lint 상한을 0 → 3으로

npm run lint는 --max-warnings 0이라 exhaustive-deps 경고 3건 때문에
현재 어떤 경우에도 통과할 수 없었다. 통과 불가능한 게이트는 무의미하고,
그렇다고 동작이 바뀔 수 있는 훅 수정을 이 커밋에 섞는 것은 더 나쁘다.
현재 개수를 상한으로 고정하는 래칫으로 바꾼다. 에러 0은 그대로 강제하고
경고가 늘어나는 것을 막는다. 남은 3건을 해소하면서 상한을 0까지
내리는 것이 목표다.

  src/features/backtest/hooks/useStrategyParams.ts:55, :90
  src/shared/hooks/useAsync.ts:85

## .dockerignore 추가 (FE)

FE에는 .dockerignore가 아예 없어 node_modules, dist, .git이 빌드
컨텍스트로 들어가고 있었다. Jenkins는 매번 새로 clone하므로 드러나지
않았지만 로컬 빌드에서는 호스트 산출물이 섞일 수 있다.

## 검증

- FE/BE 각 --target test 통과 (FE 113 passed, BE 141 passed).
- 기본 타깃 빌드 정상, 배포 이미지 산출물 동일.
- 차단 동작 확인: 일부러 실패하는 테스트를 넣으면 FE/BE 게이트 모두
  exit 1로 빌드가 중단된다. 원복 후 다시 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
7fa1154에서 build 스크립트만 고치고 build:analyze는 놓쳤다.
dev 컨테이너의 NODE_ENV=development를 그대로 물려받아
개발용 React 번들(2511 modules, react-vendor 422.18 kB)을 대상으로
"번들 크기 분석"을 하고 있었다. README가 이 명령을 번들 분석 수단으로
안내하므로, 실행한 사람이 그 숫자를 실제 번들 크기로 오해하게 된다.

수정 후 2506 modules / react-vendor 229.94 kB로 build와 동일해진다.

참고: 이 스크립트는 현재 실질적으로 build와 같은 일을 한다.
번들 분석 플러그인도 .env.analyze도 없고, vite.config.ts에서 mode는
sourcemap 판정에만 쓰이는데 analyze와 production 모두 false다.
분석 도구를 붙이거나 스크립트와 README 안내를 함께 정리하는 후속
판단이 필요하다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#47·#48·#49로 스택과 검증 체계가 바뀌었는데 문서가 따라가지 못했다.
실측한 값으로만 갱신했고, 문서에 적은 명령·경로·수치는 모두 실행해
존재를 확인했다.

## 사실과 달랐던 것

- backtest_fe/README.md: React 18 표기(실제 19), 테스트 13파일/98건
  (실제 16/113), 커버리지 17.13%(실제 21.81%), 존재하지 않는
  src/components/ 디렉터리 설명, shared 파일 수 불일치,
  끊긴 링크 4개(TEST.md 등 모두 부재)
- backtest_fe/docs/testing/execution.md: 설정 파일을 vite.config.ts로
  안내(실제 vitest.config.ts), 환경을 jsdom으로 안내(실제 happy-dom)
- backtest_be_fast/tests/README.md: 총 68건(실제 141건)
- UNIT_TEST_QUICK_REFERENCE.md: 파일별 개수 3건 불일치,
  하드코딩된 타인 절대경로(/home/coontec/...), venv 기반 실행 안내,
  실제와 다른 CI 예시(GitHub Actions/venv)
- TEST_COVERAGE_SUMMARY.md: 파일별 개수 3건 불일치, venv 실행 안내
  (합계 59건은 이 문서가 다루는 4개 모듈 기준으로 지금도 정확해 유지)
- README.md(루트): backtest_fe/__tests__/ 구조(이제 src 안으로 이동),
  스택 버전 미표기

## 새로 담은 것

- CI Quality Gate의 존재와 재현 방법(docker build --target test)
- 게이트가 배포는 막지만 병합은 막지 않는다는 점(브랜치 보호 미사용)
- type-check와 type-check:test의 분리 이유
- lint 경고 상한 3이 래칫이라는 점과 목표
- Tailwind 4 제약(설정이 index.css, @theme에 색상 리터럴 금지,
  .app-container)
- vitest isolate:false 금지 이유
- FE build의 NODE_ENV=production 고정 이유
- VITE_API_BASE_URL을 비워야 하는 계약
- build:analyze가 현재 실질적으로 build와 동일하다는 주의

CLAUDE.md와 .github/copilot-instructions.md에는 위 제약들을 에이전트가
반복해서 밟지 않도록 명시했다.

BE 문서 2건은 편집 과정에서 CRLF가 LF로 바뀌어 원래 개행으로 되돌렸다.
코드 변경은 없다(마크다운만).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kyj0503
kyj0503 merged commit dec47eb into main Aug 2, 2026
@kyj0503
kyj0503 deleted the chore/post-upgrade-cleanup branch August 2, 2026 01:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants