1284 lines
41 KiB
Markdown
1284 lines
41 KiB
Markdown
# 프론트엔드 플랫폼 테스트 전략
|
|
|
|
이 문서는 도메인 기능을 추가하기 전과 추가한 후에 프론트엔드 플랫폼의 위험을
|
|
어느 테스트 계층에서 차단할지 정의한다. 목표는 테스트 개수를 늘리는 것이 아니라,
|
|
실패 원인과 가장 가까운 계층에서 빠르고 결정적으로 결함을 발견하고 실제
|
|
브라우저·실제 production build에서만 드러나는 위험을 별도 게이트로 닫는 것이다.
|
|
|
|
이 문서는 다음 공식 문서를 기술 기준으로 사용한다.
|
|
|
|
- [Vitest 공식 가이드](https://vitest.dev/guide/)
|
|
- [Mock Service Worker 공식 문서](https://mswjs.io/docs/)
|
|
- [Playwright 공식 문서](https://playwright.dev/docs/intro)
|
|
- [Playwright 시각 비교 문서](https://playwright.dev/docs/test-snapshots)
|
|
- [Storybook UI 테스트 문서](https://storybook.js.org/docs/writing-tests)
|
|
|
|
## 1. 테스트 원칙
|
|
|
|
1. 테스트는 구현 계층이 아니라 사용자·운영 위험에서 출발한다.
|
|
2. 같은 위험을 모든 계층에서 반복해 검증하지 않는다.
|
|
3. pure policy와 codec은 빠른 unit/contract test에서 가능한 모든 분기를 닫는다.
|
|
4. 브라우저 API, React lifecycle, focus, layout은 DOM 또는 실제 브라우저에서
|
|
검증한다.
|
|
5. HTTP client를 stub하는 대신 네트워크 경계는 MSW로 검증한다.
|
|
6. production boot와 route는 개발용 component fixture만으로 완료 처리하지 않는다.
|
|
7. 성공 경로만으로 완료하지 않는다. timeout, abort, schema mismatch, 권한,
|
|
충돌, 재시도 소진을 포함한다.
|
|
8. 접근성 자동 검사와 시각 회귀는 서로 대체하지 않는다.
|
|
9. coverage 숫자는 누락 탐지 신호이며 behavior 완료 증거가 아니다.
|
|
10. flaky test를 자동 재시도로 숨기거나 `skip` 상태로 무기한 유지하지 않는다.
|
|
11. test-only 우회 경로와 mock credential을 production bundle에 포함하지 않는다.
|
|
12. 모든 CI gate는 실패 시 종료 코드가 non-zero여야 하며
|
|
`continue-on-error`로 낮추지 않는다.
|
|
|
|
## 2. 현재 기준선
|
|
|
|
현재 저장소는 테스트 계층과 CI 증거 경로를 명시적으로 분리한다.
|
|
|
|
### 2.1 실행 명령
|
|
|
|
`package.json`에는 다음 계층이 있다.
|
|
|
|
- `test:runtime-schema`
|
|
- `test:unit`
|
|
- `test:component`
|
|
- `test:integration`
|
|
- `test:e2e`
|
|
- `test:a11y`
|
|
- `review:a11y-manual`
|
|
- `test:sample-removal`
|
|
- `test:performance`
|
|
|
|
Vitest 결과는 JUnit으로 `artifacts/tests/`에 기록되고 Playwright는 HTML report,
|
|
trace, failure screenshot을 보존한다. `config/ci/gates.json`은 품질 gate와 증거
|
|
경로를 등록하며 `.gitea/workflows/quality-gates.yml`은 merge/release/production/
|
|
field/documentation 단계를 구성한다.
|
|
|
|
### 2.2 확인된 강점
|
|
|
|
- runtime config와 release manifest schema test가 분리되어 있다.
|
|
- retry, error classification, compatibility, performance policy 같은 pure logic에
|
|
unit test가 있다.
|
|
- React Testing Library와 `user-event`로 component behavior를 검사한다.
|
|
- MSW Node server로 HTTP 경계를 실제 `fetch` 수준에서 격리한다.
|
|
- 잘못된 architecture/security/type fixture가 실제로 거절되는지 검증한다.
|
|
- Chromium, Firefox, WebKit 프로젝트가 구성되어 있다.
|
|
- 등록된 모든 route에 axe 검사가 있다.
|
|
- 320px reflow와 mobile navigation을 E2E로 확인한다.
|
|
- release build의 bundle과 lab performance budget이 별도 gate다.
|
|
|
|
### 2.3 확인된 공백
|
|
|
|
#### 테스트 TypeScript가 typecheck 대상이 아니다
|
|
|
|
현재 `check:types`는 `tsconfig.json`의 include 범위에 의존하며 tests 전체의
|
|
callback, mock, fixture, custom matcher type을 별도 프로젝트로 검사하지 않는다.
|
|
TypeScript 전환 뒤 Vitest가 test file을 변환해 실행할 수 있다는 사실은
|
|
`tsc` typecheck를 대체하지 않는다.
|
|
|
|
#### 실제 bootstrap integration test가 없다
|
|
|
|
`tests/component/bootstrap-shell.test.jsx`는 production bootstrap을 import하지
|
|
않고 테스트 내부의 `<TestShell>`만 렌더링한다. E2E는 실제 entry를 통과하지만,
|
|
다음 실패를 작은 통합 테스트에서 식별하기 어렵다.
|
|
|
|
- runtime config fetch 실패
|
|
- config/manifest mismatch
|
|
- adapter composition 실패
|
|
- provider 순서 또는 누락
|
|
- external auth owner 유무
|
|
- product tree를 마운트하기 전 fail-closed
|
|
- boot error shell의 safe metadata
|
|
- StrictMode와 unmount cleanup
|
|
|
|
#### TanStack Query의 React integration test가 없다
|
|
|
|
Query cache adapter의 명령형 `read/write/invalidate` unit test는 있지만
|
|
`useQuery`, `useMutation`, cancellation, stale/background refresh, optimistic
|
|
rollback을 사용하는 production presentation adapter가 아직 없다. 따라서 query
|
|
provider가 마운트되어도 React 사용자의 실제 상태 전환은 검증되지 않는다.
|
|
|
|
#### Form 테스트가 단일 TextField 흐름에 머문다
|
|
|
|
현재 component/E2E는 label, description, error association과 빈 값 submit을
|
|
검사한다. error summary, 첫 오류 focus, async validation race, 422 field error,
|
|
double submit, dirty navigation, mutation conflict는 없다.
|
|
|
|
#### Route registry와 실행 tree가 별도로 테스트된다
|
|
|
|
registry snapshot과 일부 navigation/access policy test는 있으나 다음 계약을
|
|
강제하지 않는다.
|
|
|
|
- 모든 route ID에 lazy runtime module이 존재하는가
|
|
- params/search가 실제 codec으로 검증되는가
|
|
- deep link와 basename refresh가 동작하는가
|
|
- chunk load failure가 1회 reload/support surface로 연결되는가
|
|
- route error boundary가 location 변경 시 reset되는가
|
|
- scroll restoration과 form navigation blocker가 동작하는가
|
|
|
|
#### Storybook과 시각 회귀가 없다
|
|
|
|
`/examples/ui`는 통합 gallery지만 component별 모든 state를 격리하지 않는다.
|
|
Storybook story, interaction story, story-level axe, `toHaveScreenshot()` baseline이
|
|
없다. `screenshot: "only-on-failure"`는 디버깅 증거이며 시각 회귀 테스트가 아니다.
|
|
|
|
#### MSW scenario가 공유되지 않는다
|
|
|
|
integration file마다 `setupServer`, handler, response body를 다시 정의한다.
|
|
Node integration, Storybook browser, feature component test, E2E mock service가 같은
|
|
시나리오 이름과 contract fixture를 공유하지 않는다.
|
|
|
|
#### E2E가 개발 서버를 대상으로 한다
|
|
|
|
현재 Playwright web server는 `pnpm dev`다. route behavior 확인에는 유효하지만
|
|
다음 release 위험은 production build/preview에서만 확인할 수 있다.
|
|
|
|
- hashed lazy chunk
|
|
- source 변환과 tree shaking
|
|
- base path
|
|
- deep-link fallback
|
|
- build-time environment
|
|
- release manifest와 runtime config 조합
|
|
- minified code의 chunk failure
|
|
|
|
#### Coverage가 실행·차단되지 않는다
|
|
|
|
`vitest.config.js`에는 reporter만 선언되어 있고 coverage provider, script,
|
|
threshold, diff policy가 없다.
|
|
|
|
## 3. 위험 기반 테스트 계층
|
|
|
|
우선순위 의미:
|
|
|
|
- `P0`: 오류가 발생하면 애플리케이션 부팅, 데이터 무결성, 인증, 주요 사용 흐름,
|
|
보안 또는 접근성이 깨지는 위험
|
|
- `P1`: 주요 기능 품질, 브라우저 호환성, 운영 복구와 개발 생산성을 저하하는 위험
|
|
- `P2`: 고급 기능 또는 특정 제품 profile에서만 필요한 위험
|
|
|
|
| 위험 | 우선순위 | 가장 가까운 테스트 | 추가 증거 |
|
|
| --- | --- | --- | --- |
|
|
| runtime config/manifest mismatch | P0 | schema + bootstrap integration | built-dist smoke |
|
|
| secret-like client config | P0 | negative contract fixture | browser security gate |
|
|
| HTTP request/response schema mismatch | P0 | adapter integration + MSW | feature integration |
|
|
| 중복 retry 또는 unsafe mutation retry | P0 | unit policy + MSW integration | E2E degraded scenario |
|
|
| 401 복구 loop | P0 | auth/HTTP integration | protected route E2E |
|
|
| query cancellation/stale state 오류 | P0 | query hook integration | route E2E |
|
|
| optimistic mutation rollback 오류 | P0 | mutation integration | visual state smoke |
|
|
| form validation/422 mapping 오류 | P0 | form component integration | route E2E |
|
|
| route access/deep-link 오류 | P0 | route contract/component | built-dist E2E |
|
|
| lazy chunk 복구 loop | P0 | use-case unit + release E2E | runbook drill |
|
|
| keyboard/focus/accessible name 오류 | P0 | component + story a11y | E2E/manual |
|
|
| theme/token contrast 오류 | P0 | token contract + axe | visual/manual |
|
|
| responsive overflow | P1 | component layout + E2E | visual |
|
|
| 브라우저 엔진 차이 | P1 | 3-engine E2E | manual device smoke |
|
|
| visual regression | P1 | pinned Chromium screenshot | reviewer approval |
|
|
| i18n/RTL/long text clipping | P1 | story + visual | route smoke |
|
|
| telemetry redaction | P0 | unit/adapter integration | bootstrap smoke |
|
|
| bundle 증가 | P1 | build manifest budget | release gate |
|
|
| render/performance 저하 | P1 | component profiler 선택 | lab/field metrics |
|
|
| virtualized collection | P2 | browser component | large-data E2E |
|
|
| offline/PWA persistence | P2 | service-worker integration | installable build E2E |
|
|
|
|
## 4. TypeScript 테스트 계약
|
|
|
|
TypeScript 전환 후 production과 test typecheck를 분리하되 둘 다 merge gate로
|
|
차단한다.
|
|
|
|
### 4.1 목표 구성
|
|
|
|
```text
|
|
tsconfig.base.json
|
|
tsconfig.app.json
|
|
tsconfig.node.json
|
|
tsconfig.test.json
|
|
tsconfig.json # project references
|
|
```
|
|
|
|
`tsconfig.test.json`에는 다음을 포함한다.
|
|
|
|
- `tests/**/*.ts`
|
|
- `tests/**/*.tsx`
|
|
- `src/**/*.stories.ts`
|
|
- `src/**/*.stories.tsx`
|
|
- `tests/setup.ts`
|
|
- custom matcher type
|
|
- MSW handlers와 factories
|
|
- Playwright tests는 필요하면 별도 `tsconfig.e2e.json`
|
|
|
|
권장 명령:
|
|
|
|
```bash
|
|
tsc --noEmit -p tsconfig.app.json
|
|
tsc --noEmit -p tsconfig.test.json
|
|
tsc --noEmit -p tsconfig.e2e.json
|
|
```
|
|
|
|
### 4.2 Type-level test
|
|
|
|
다음은 runtime test가 아니라 compile-time test로 닫는다.
|
|
|
|
- route ID와 runtime route map의 exhaustive mapping
|
|
- `routePath` params 누락과 잘못된 key
|
|
- query key factory input/output
|
|
- component variant closed union
|
|
- form schema와 default value shape
|
|
- use case command/result
|
|
- public port 구현 누락
|
|
- vendor type이 public facade 밖으로 새는 문제
|
|
|
|
필요하면 Vitest의 `expectTypeOf`를 사용하되 실제 `tsc`도 실행한다. 의도적으로
|
|
실패해야 하는 fixture는 독립 `tsconfig`와 `@ts-expect-error` 설명을 사용한다.
|
|
부정 fixture가 실수로 통과하면 gate가 실패해야 한다.
|
|
|
|
### 4.3 금지 사항
|
|
|
|
- test file을 `any`로 우회
|
|
- `as unknown as`로 mock contract를 무조건 통과
|
|
- production type error를 test mock에서 숨김
|
|
- test는 실행되므로 typecheck도 된다고 가정
|
|
- 의도와 설명 없는 `@ts-ignore`
|
|
|
|
## 5. Unit과 Contract 테스트
|
|
|
|
### 5.1 Unit 대상
|
|
|
|
DOM과 network 없이 결정 가능한 로직은 unit test로 검증한다.
|
|
|
|
- error classification
|
|
- retry eligibility, attempt cap, backoff와 jitter bound
|
|
- query key canonicalization
|
|
- route access decision
|
|
- params/search codec
|
|
- locale formatter wrapper
|
|
- token/variant static map
|
|
- async view-state reducer
|
|
- form DTO mapper
|
|
- optimistic patch/rollback reducer
|
|
- chunk recovery decision
|
|
- redaction
|
|
- performance/readiness formula
|
|
|
|
시간, random, UUID는 주입하거나 fake timer로 통제한다. 실제 `setTimeout`을
|
|
기다리는 test를 만들지 않는다.
|
|
|
|
### 5.2 Contract 대상
|
|
|
|
registry와 외부 boundary의 안정된 shape를 검증한다.
|
|
|
|
- runtime config schema
|
|
- release manifest schema
|
|
- API operation registry
|
|
- request/response schema registry
|
|
- error registry
|
|
- route registry/runtime map
|
|
- storage key registry
|
|
- telemetry event registry
|
|
- design token registry
|
|
- mock scenario registry
|
|
|
|
inline snapshot은 사람이 검토 가능한 작고 안정된 registry에만 사용한다.
|
|
큰 payload, DOM tree, CSS 전체를 snapshot으로 고정하지 않는다.
|
|
|
|
### 5.3 Negative fixture
|
|
|
|
positive test만으로 control을 증명하지 않는다.
|
|
|
|
```text
|
|
tests/fixtures/
|
|
architecture/
|
|
allowed/
|
|
forbidden/
|
|
security/
|
|
allowed/
|
|
forbidden/
|
|
typecheck/
|
|
allowed/
|
|
forbidden/
|
|
routes/
|
|
malformed-search/
|
|
contracts/
|
|
malformed-envelope/
|
|
```
|
|
|
|
부정 fixture는 실제 production validator와 동일한 entry로 실행한다.
|
|
|
|
## 6. 실제 Bootstrap Integration
|
|
|
|
bootstrap test는 production entry의 로직을 재작성하지 않는다. `main.tsx`는 DOM
|
|
탐색과 최종 호출만 남기고, 테스트 가능한 함수로 boot를 추출한다.
|
|
|
|
목표 API 예:
|
|
|
|
```ts
|
|
bootstrapApplication({
|
|
rootElement,
|
|
fetcher,
|
|
host,
|
|
buildConfig,
|
|
createRoot,
|
|
});
|
|
```
|
|
|
|
production은 실제 dependency를 넘기고 integration test는 deterministic
|
|
dependency를 넘긴다.
|
|
|
|
### 6.1 필수 시나리오
|
|
|
|
| 시나리오 | 기대 |
|
|
| --- | --- |
|
|
| valid config + coherent manifest | product tree가 한 번 마운트 |
|
|
| config fetch network failure | product tree 미마운트, safe boot shell |
|
|
| config non-JSON | safe code만 표시 |
|
|
| forbidden secret-like key | fail-closed |
|
|
| config schema mismatch | issue detail/endpoint/body 비노출 |
|
|
| manifest fetch/shape failure | safe release failure |
|
|
| config/build/release mismatch | product tree 미마운트 |
|
|
| external auth owner 존재 | external session adapter 선택 |
|
|
| external auth owner 누락 | integration-failed session |
|
|
| local demo auth | local/development에서만 허용 |
|
|
| telemetry disabled | network/queue side effect 없음 |
|
|
| storage unavailable | memory/failure policy대로 boot |
|
|
| StrictMode | subscription/cleanup leak 없음 |
|
|
| unmount | listener, timer, pending request 정리 |
|
|
|
|
### 6.2 Provider 조립 검증
|
|
|
|
현재 production provider 순서를 사실대로 검증한다.
|
|
|
|
```text
|
|
QueryClientProvider
|
|
-> BrowserRouter
|
|
-> ThemeProvider
|
|
-> SessionProvider
|
|
-> AppShell
|
|
```
|
|
|
|
각 provider를 mock component로 대체해 문자열 순서만 검사하지 않는다. 실제
|
|
Context/hook을 소비하는 작은 probe route를 렌더링해 provider가 사용 가능한지
|
|
확인한다.
|
|
|
|
application input facade를 도입한 목표 순서는 다음과 같다.
|
|
|
|
```text
|
|
QueryClientProvider
|
|
-> ApplicationProvider
|
|
-> RouterProvider
|
|
-> ThemeProvider
|
|
-> SessionProvider
|
|
-> AppShell
|
|
```
|
|
|
|
04 routing branch에서 Data Mode로 전환할 때 `BrowserRouter`를
|
|
`RouterProvider`로 바꾸고 테스트 fixture도 같은 composition factory에서
|
|
생성한다. 문서에 적힌 provider 순서를 테스트 전용 shell로 재현하지 말고
|
|
production composition 함수를 호출한다.
|
|
|
|
### 6.3 Boot E2E
|
|
|
|
bootstrap integration이 통과해도 다음은 production build smoke로 다시 확인한다.
|
|
|
|
- `/config.json` no-store fetch
|
|
- `/release-manifest.json` coherence
|
|
- base path
|
|
- hashed asset reachability
|
|
- boot error shell
|
|
- public route
|
|
- protected route
|
|
|
|
## 7. Query와 Mutation 테스트
|
|
|
|
TanStack Query를 canonical React inbound adapter 뒤에 연결한 뒤 다음 계층을
|
|
제공한다.
|
|
|
|
```text
|
|
Page / AsyncSurface / Form pattern
|
|
-> local query/mutation hook
|
|
-> TanStack Query lifecycle
|
|
-> application input use case
|
|
-> outbound gateway
|
|
```
|
|
|
|
### 7.1 Query 필수 시나리오
|
|
|
|
- 최초 pending은 loading surface를 표시한다.
|
|
- 성공 payload는 view model로 렌더링된다.
|
|
- 빈 array와 빈 page는 empty로 분류된다.
|
|
- background refetch 중 기존 data를 유지한다.
|
|
- stale + degraded 상태를 표시한다.
|
|
- 같은 query key 요청은 deduplicate된다.
|
|
- filter 순서가 달라도 canonical key가 같다.
|
|
- route unmount 또는 superseded input에서 request를 abort한다.
|
|
- aborted request는 terminal error나 telemetry failure로 오분류되지 않는다.
|
|
- offline/paused와 loading을 구분한다.
|
|
- 401 복구는 한 번만 수행한다.
|
|
- 재로그인 후 허용된 operation만 다시 실행한다.
|
|
- schema mismatch는 safe terminal failure다.
|
|
- retryable failure만 bounded retry한다.
|
|
- query error reset 후 다시 성공할 수 있다.
|
|
|
|
각 test는 새로운 `QueryClient`를 생성하고 retry를 명시적으로 통제한다. singleton
|
|
cache를 test 사이에 공유하지 않는다.
|
|
|
|
### 7.2 Mutation 필수 시나리오
|
|
|
|
- submit은 한 번만 전송된다.
|
|
- keyed mutation은 동일 logical action에서 idempotency key를 유지한다.
|
|
- 성공 후 올바른 query namespace만 invalidate한다.
|
|
- optimistic update와 success reconcile이 일치한다.
|
|
- 실패 시 이전 cache로 rollback한다.
|
|
- 409 conflict를 일반 500과 구분한다.
|
|
- 422 field error는 safe field map으로 전달한다.
|
|
- non-idempotent mutation은 자동 재시도하지 않는다.
|
|
- unmount 후 state update warning이 없다.
|
|
- 사용자 취소와 timeout을 구분한다.
|
|
- mutation pending 중 중복 action이 차단된다.
|
|
|
|
### 7.3 Test helper
|
|
|
|
```text
|
|
tests/support/query/
|
|
create-test-query-client.ts
|
|
render-with-query.tsx
|
|
wait-for-query-idle.ts
|
|
```
|
|
|
|
helper는 production default를 복사하지 않는다. production factory를 호출하고
|
|
필요한 시간·retry만 test override로 주입한다.
|
|
|
|
## 8. Form 테스트
|
|
|
|
Form test는 field primitive, form controller, application command mapping을
|
|
분리한다.
|
|
|
|
### 8.1 Field primitive
|
|
|
|
- visible label과 accessible name
|
|
- description/error `aria-describedby`
|
|
- `aria-invalid`
|
|
- required/readonly/disabled
|
|
- ref와 focus
|
|
- autocomplete/inputmode
|
|
- IME composition
|
|
- controlled/uncontrolled
|
|
|
|
### 8.2 Form controller
|
|
|
|
- default value
|
|
- touched/dirty
|
|
- submit validation 시점
|
|
- error summary
|
|
- 첫 invalid field focus
|
|
- async validation race와 취소
|
|
- server 422 field/global error
|
|
- pending/double submit
|
|
- reset과 server value reinitialize
|
|
- unsaved navigation blocker
|
|
|
|
### 8.3 Application 경계
|
|
|
|
- UI string을 command DTO로 normalize
|
|
- 빈 문자열/null/undefined 정책
|
|
- locale number/date parse
|
|
- client validation 통과 후에도 application/domain validation 수행
|
|
- server field name을 승인된 UI field에만 매핑
|
|
- 알 수 없는 server path는 form-level safe error로 이동
|
|
- raw backend message를 직접 표시하지 않음
|
|
|
|
### 8.4 E2E 최소 흐름
|
|
|
|
1. keyboard만으로 field를 이동한다.
|
|
2. 빈 submit 후 error summary로 focus가 이동한다.
|
|
3. summary link로 field에 이동한다.
|
|
4. 값을 수정한다.
|
|
5. mutation pending을 확인한다.
|
|
6. success 또는 conflict/422를 확인한다.
|
|
7. success 후 dirty guard가 해제된다.
|
|
|
|
## 9. Router 테스트
|
|
|
|
### 9.1 Contract
|
|
|
|
- route ID는 unique하다.
|
|
- path와 navigation order가 unique하다.
|
|
- 모든 route ID에 runtime module이 있다.
|
|
- 모든 runtime module은 registry route ID를 참조한다.
|
|
- params/search codec이 실제 객체다.
|
|
- loading/error surface가 등록된 component key다.
|
|
- route title은 typed message key다.
|
|
|
|
### 9.2 Component/integration
|
|
|
|
- public route
|
|
- session-required route
|
|
- recovery-pending route
|
|
- integration-failed route
|
|
- forbidden result
|
|
- not-found route
|
|
- params success/failure
|
|
- search default/invalid/canonical serialization
|
|
- same-page query change
|
|
- route heading focus
|
|
- document title
|
|
- scroll restoration
|
|
- redirect loop guard
|
|
- dirty form blocker
|
|
- error boundary reset
|
|
|
|
### 9.3 Built-dist E2E
|
|
|
|
- direct deep link
|
|
- browser refresh
|
|
- basename 배포
|
|
- back/forward
|
|
- lazy route navigation
|
|
- chunk request failure
|
|
- old HTML/new manifest mismatch
|
|
- 1회 reload guard
|
|
- 404 fallback
|
|
|
|
client route guard는 UX이며 authorization이 아님을 test 이름과 문서에서 유지한다.
|
|
403은 server 결과로도 별도 검증한다.
|
|
|
|
## 10. Design System 테스트
|
|
|
|
공용 컴포넌트는 한 번의 결함이 모든 페이지로 전파되므로 위험도가 높다.
|
|
|
|
### 10.1 Component behavior
|
|
|
|
- native role/name/value
|
|
- keyboard matrix
|
|
- focus-visible
|
|
- disabled/readonly/pending
|
|
- controlled/uncontrolled
|
|
- ref
|
|
- callback 횟수와 payload
|
|
- portal cleanup
|
|
- overlay focus trap/restore
|
|
- live-region announcement
|
|
- reduced motion
|
|
|
|
class 이름 자체보다 사용자에게 관찰 가능한 behavior를 우선한다. variant class
|
|
map의 exhaustive 여부는 unit/type test에서 별도로 검사한다.
|
|
|
|
### 10.2 Token contract
|
|
|
|
- 필수 semantic token 존재
|
|
- light/dark 모두 정의
|
|
- component가 raw 색상 사용하지 않음
|
|
- focus/status contrast
|
|
- forced-colors fallback
|
|
- 반복 arbitrary value 차단
|
|
- token gallery와 registry 일치
|
|
|
|
### 10.3 Story matrix
|
|
|
|
모든 primitive/pattern은 다음 story를 가진다.
|
|
|
|
- default
|
|
- variants
|
|
- disabled/readonly
|
|
- pending/loading
|
|
- invalid/error
|
|
- long text
|
|
- compact
|
|
- dark
|
|
- pseudo-locale
|
|
- RTL
|
|
|
|
overlay는 open, keyboard, outside dismiss, long content, nested content를 추가한다.
|
|
form은 client/server error와 async pending을 추가한다.
|
|
|
|
### 10.4 접근성
|
|
|
|
Storybook a11y를 기본 `error`로 설정한다. route axe가 초기 화면만 검사하는 공백을
|
|
open dialog, invalid form, expanded menu 같은 state story로 보완한다.
|
|
|
|
자동 axe 결과가 없더라도 다음을 component behavior로 직접 검사한다.
|
|
|
|
- focus order
|
|
- focus trap/restore
|
|
- keyboard shortcut
|
|
- live announcement의 중복
|
|
- visible focus
|
|
|
|
## 11. Shared MSW Scenario Catalog
|
|
|
|
[MSW](https://mswjs.io/docs/)는 request client를 mock하지 않고 HTTP 경계에서
|
|
요청을 가로챈다. Node integration과 browser workshop이 동일한 operation contract
|
|
및 scenario vocabulary를 사용하도록 중앙 catalog를 만든다.
|
|
|
|
### 11.1 목표 구조
|
|
|
|
```text
|
|
tests/mocks/
|
|
contracts/
|
|
envelopes.ts
|
|
payloads.ts
|
|
handlers/
|
|
runtime-config.ts
|
|
release-manifest.ts
|
|
resources.ts
|
|
scenarios/
|
|
catalog.ts
|
|
create-scenario-handlers.ts
|
|
server.ts
|
|
browser.ts
|
|
|
|
tests/factories/
|
|
failure-factory.ts
|
|
resource-factory.ts
|
|
session-owner-factory.ts
|
|
```
|
|
|
|
### 11.2 공통 scenario ID
|
|
|
|
| ID | 응답 |
|
|
| --- | --- |
|
|
| `success` | 유효 envelope와 data |
|
|
| `empty` | 유효한 빈 collection |
|
|
| `slow` | 통제된 지연 |
|
|
| `network-error` | transport 실패 |
|
|
| `timeout` | client timeout 초과 |
|
|
| `aborted` | navigation/user/superseded abort |
|
|
| `content-type-mismatch` | JSON이 아닌 content type |
|
|
| `malformed-json` | 파싱 불가능한 JSON |
|
|
| `envelope-mismatch` | top-level contract 위반 |
|
|
| `schema-mismatch` | data payload contract 위반 |
|
|
| `auth-recover-once` | 첫 401 후 복구 성공 |
|
|
| `auth-persistent-401` | 복구 뒤에도 401 |
|
|
| `forbidden-403` | 권한 없음 |
|
|
| `not-found-404` | 리소스 없음 |
|
|
| `conflict-409` | mutation conflict |
|
|
| `validation-422` | 승인된 field/global error |
|
|
| `rate-limited-429` | Retry-After 포함 |
|
|
| `server-retry-success` | bounded retry 뒤 성공 |
|
|
| `server-terminal-500` | retry 소진 |
|
|
|
|
### 11.3 Catalog 규칙
|
|
|
|
- scenario ID는 typed closed set이다.
|
|
- operation별 허용 scenario를 명시한다.
|
|
- factory default는 고정 값이며 random에 의존하지 않는다.
|
|
- timestamp/UUID가 필요하면 seeded factory를 사용한다.
|
|
- raw fixture에는 실제 개인정보·token을 넣지 않는다.
|
|
- test가 override한 handler는 `afterEach`에서 reset한다.
|
|
- 처리되지 않은 request는 `onUnhandledRequest: "error"`로 실패한다.
|
|
- test가 expected request count와 중요한 header/body를 검증한다.
|
|
- retry test는 attempt counter를 test 내부에서 초기화한다.
|
|
- scenario response는 production schema validator도 통과하거나 의도적으로
|
|
실패해야 한다.
|
|
|
|
### 11.4 환경별 사용
|
|
|
|
```text
|
|
Vitest Node
|
|
-> setupServer(...handlers)
|
|
|
|
Storybook
|
|
-> setupWorker(...handlers)
|
|
|
|
Playwright release E2E
|
|
-> shared scenario catalog로 생성한 local mock API
|
|
또는 동일 fixture builder를 사용하는 page.route
|
|
```
|
|
|
|
MSW browser worker와 scenario selector는 development/test entry에서만 import한다.
|
|
production runtime config에 `MOCK_MODE`, `SCENARIO`, fake token 같은 key를 추가하지
|
|
않는다.
|
|
|
|
## 12. Storybook 테스트
|
|
|
|
Storybook은 component를 격리해 state를 열거하고 실제 브라우저 interaction과
|
|
접근성을 검사하는 개발·CI surface다. `/examples/ui`는 앱 통합 smoke로 유지하되
|
|
Storybook을 대신하지 않는다.
|
|
|
|
### 12.1 Global decorator
|
|
|
|
- semantic token stylesheet
|
|
- ThemeProvider
|
|
- LocaleProvider
|
|
- Memory Router
|
|
- SessionProvider test owner
|
|
- QueryClientProvider
|
|
- MSW browser worker
|
|
- portal root
|
|
|
|
모든 story가 production provider와 다른 임의 wrapper를 만들지 않도록
|
|
`renderStory` helper를 하나만 둔다.
|
|
|
|
### 12.2 Interaction test
|
|
|
|
Storybook `play` 함수는 다음에 사용한다.
|
|
|
|
- menu/dialog/drawer 열기와 닫기
|
|
- keyboard navigation
|
|
- form validation
|
|
- toast dismiss
|
|
- tab/pagination selection
|
|
- optimistic mutation state
|
|
|
|
같은 세부 behavior를 RTL unit과 story에 모두 복사하지 않는다. pure DOM behavior는
|
|
빠른 component test, 여러 provider/network/browser interaction은 story에 둔다.
|
|
|
|
### 12.3 Story a11y
|
|
|
|
- 기본 policy는 error
|
|
- rule disable은 component/story별 owner, 사유, 만료일 필요
|
|
- critical/serious만이 아니라 목표 WCAG tag의 모든 violation을 검토
|
|
- incomplete 결과는 수동 검토 목록으로 전달
|
|
|
|
### 12.4 Story build
|
|
|
|
`build-storybook`은 merge gate다. 다음을 차단한다.
|
|
|
|
- 깨진 import
|
|
- 누락된 provider
|
|
- vendor facade 교체 오류
|
|
- story type error
|
|
- public component가 isolated build에서만 실패하는 문제
|
|
|
|
Storybook static artifact를 production application artifact와 합치지 않는다.
|
|
|
|
## 13. Playwright 전략
|
|
|
|
### 13.1 두 실행 profile
|
|
|
|
#### 개발 피드백
|
|
|
|
- Vite dev server
|
|
- 빠른 Chromium 중심 smoke
|
|
- 로컬 `reuseExistingServer`
|
|
- 개발 중 route와 form 확인
|
|
|
|
#### Release 검증
|
|
|
|
```text
|
|
pnpm build
|
|
-> pnpm preview
|
|
-> Playwright release config
|
|
```
|
|
|
|
- CI에서 기존 server 재사용 금지
|
|
- production `dist`
|
|
- 실제 runtime config/release manifest
|
|
- base path와 hashed chunks
|
|
- trace, video 또는 screenshot evidence
|
|
|
|
merge gate의 주요 E2E도 가능한 한 built-dist를 사용하고, dev E2E는 개발 편의
|
|
명령으로 분리한다.
|
|
|
|
### 13.2 3-engine 정책
|
|
|
|
다음 critical smoke는 Chromium, Firefox, WebKit 모두 실행한다.
|
|
|
|
- boot
|
|
- public route navigation
|
|
- protected route/session
|
|
- form submit/error
|
|
- dialog/drawer/menu keyboard
|
|
- theme
|
|
- 320px reflow
|
|
- automated a11y
|
|
- deep link
|
|
- lazy chunk
|
|
|
|
고비용 exhaustive visual matrix는 pinned Chromium 하나로 실행한다. 엔진 차이
|
|
위험이 큰 native dialog, date/input, focus behavior는 WebKit과 Firefox에서도
|
|
별도 behavior assertion을 유지한다.
|
|
|
|
### 13.3 Mobile과 responsive
|
|
|
|
최소 viewport:
|
|
|
|
- 320x720: 최소 reflow
|
|
- 390x844: 일반 mobile navigation/touch
|
|
- 768x1024: tablet
|
|
- 1280x720: desktop
|
|
- 1440x900: wide layout
|
|
|
|
검증:
|
|
|
|
- horizontal overflow
|
|
- sticky header/sidebar
|
|
- mobile drawer focus
|
|
- touch target
|
|
- long translated text
|
|
- landscape
|
|
- software keyboard로 가려질 수 있는 form action
|
|
- dialog max height와 scroll
|
|
- reduced motion
|
|
- dark theme
|
|
|
|
device descriptor는 browser/device 특성을 흉내 내지만 실제 기기를 완전히
|
|
대체하지 않는다. 제품 release policy가 요구하면 iOS Safari/Android Chrome
|
|
수동 또는 device-farm smoke를 추가한다.
|
|
|
|
### 13.4 시각 회귀
|
|
|
|
Playwright `toHaveScreenshot()`을 사용한다.
|
|
|
|
필수 baseline:
|
|
|
|
- app shell compact/wide
|
|
- 각 page template
|
|
- 모든 design-system primitive group
|
|
- loading/empty/error/401/403/404
|
|
- invalid form
|
|
- open dialog/drawer/menu
|
|
- light/dark
|
|
- pseudo-locale/RTL 대표
|
|
|
|
결정성 규칙:
|
|
|
|
- OS, browser, font, device scale을 CI image로 고정
|
|
- animation/caret 비활성화
|
|
- clock/random/network fixture 고정
|
|
- 개인정보와 동적 ID mask
|
|
- update snapshot은 전용 명령과 reviewer 승인을 요구
|
|
- threshold를 높여 실제 차이를 숨기지 않는다.
|
|
|
|
Chromatic 같은 cloud visual service는 선택이다. 저장소 내부 Playwright baseline은
|
|
외부 서비스 없이도 실행 가능해야 한다.
|
|
|
|
### 13.5 실패 증거
|
|
|
|
- trace: first retry 또는 최종 실패
|
|
- screenshot: 실패 상태
|
|
- HTML report
|
|
- console/page error
|
|
- network failure summary
|
|
- release/build ID
|
|
|
|
CI retry를 허용하는 경우 첫 실패 trace도 폐기하지 않는다. retry 후 통과한 test는
|
|
flaky signal로 집계한다.
|
|
|
|
## 14. Coverage 정책
|
|
|
|
Coverage 목표는 “99% 완성도”와 같은 표현을 숫자로 대신하기 위한 것이 아니다.
|
|
높은 위험의 branch가 실행되었는지 확인하는 보조 gate다.
|
|
|
|
### 14.1 도구
|
|
|
|
Vitest coverage provider를 명시적으로 설치하고 `coverage` script를 추가한다.
|
|
Vite/Vitest 조합에서는 V8 provider를 기본 후보로 사용하되 source map 정확성을
|
|
fixture로 확인한다.
|
|
|
|
필수 report:
|
|
|
|
- text
|
|
- JSON summary
|
|
- LCOV 또는 Cobertura
|
|
|
|
### 14.2 위험별 목표
|
|
|
|
| 영역 | Line/statement 참고 | Branch 우선 목표 |
|
|
| --- | --- | --- |
|
|
| registry/codec/schema | 95~100% | 100%에 가깝게 |
|
|
| retry/error/auth/chunk policy | 95~100% | 모든 결정 분기 |
|
|
| HTTP/storage/telemetry adapter | 90% 이상 | 모든 failure class |
|
|
| query/mutation/form controller | 90% 이상 | 상태 전이 전부 |
|
|
| design-system primitive | 숫자보다 state matrix | keyboard/error/open 전부 |
|
|
| route/page composition | 80% 이상 참고 | critical route 전부 |
|
|
| scripts/release checks | 90% 이상 | positive/negative fixture |
|
|
|
|
전체 global threshold 하나만 두면 쉬운 파일로 위험한 파일의 누락을 가릴 수 있다.
|
|
glob 또는 별도 package/명령으로 high-risk module threshold를 강화한다.
|
|
|
|
### 14.3 Diff coverage
|
|
|
|
신규·변경 코드의 executable line과 branch는 기본적으로 테스트되어야 한다.
|
|
예외는 다음을 기록한다.
|
|
|
|
- 실행 불가능한 defensive line
|
|
- browser engine 전용 branch
|
|
- external evidence만 가능한 branch
|
|
- owner
|
|
- 사유
|
|
- 만료일 또는 제거 조건
|
|
|
|
### 14.4 제외
|
|
|
|
다음만 명시적으로 제외한다.
|
|
|
|
- generated type
|
|
- build output
|
|
- story metadata 자체
|
|
- 순수 type-only file
|
|
- schema에서 생성된 code
|
|
|
|
bootstrap, facade, error fallback을 “테스트가 어렵다”는 이유로 제외하지 않는다.
|
|
|
|
## 15. Fixture와 테스트 디렉터리
|
|
|
|
현재 중앙 test taxonomy를 유지하면서 책임을 명확히 한다.
|
|
|
|
```text
|
|
tests/
|
|
runtime-schema/
|
|
unit/
|
|
contract/
|
|
component/
|
|
integration/
|
|
bootstrap/
|
|
query/
|
|
forms/
|
|
features/
|
|
e2e/
|
|
smoke/
|
|
routes/
|
|
forms/
|
|
accessibility/
|
|
visual/
|
|
mocks/
|
|
contracts/
|
|
handlers/
|
|
scenarios/
|
|
factories/
|
|
fixtures/
|
|
architecture/
|
|
security/
|
|
typecheck/
|
|
contracts/
|
|
support/
|
|
render/
|
|
query/
|
|
router/
|
|
clock/
|
|
setup.ts
|
|
```
|
|
|
|
Story는 component와 가까이 둔다.
|
|
|
|
```text
|
|
src/adapters/inbound/react/design-system/primitives/button/
|
|
button.tsx
|
|
button.css
|
|
button.stories.tsx
|
|
```
|
|
|
|
### 15.1 역할 구분
|
|
|
|
- `factory`: 유효한 typed object를 기본값과 override로 생성
|
|
- `fixture`: 변경하지 않는 positive/negative 샘플
|
|
- `handler`: operation request를 intercept
|
|
- `scenario`: 여러 handler와 상태를 사용자 흐름으로 조합
|
|
- `support`: render wrapper, deterministic clock, test query client
|
|
|
|
factory가 production schema를 우회하지 않게 contract test에서 생성 결과를
|
|
validate한다.
|
|
|
|
### 15.2 파일 이름
|
|
|
|
- unit/component/integration: `*.test.ts`, `*.test.tsx`
|
|
- Playwright: `*.spec.ts`
|
|
- story: `*.stories.tsx`
|
|
- negative fixture는 기대 실패 이유를 directory/name으로 표현
|
|
|
|
`utils.ts`, `helpers.ts`, `fixture.ts` 같은 의미 없는 단일 파일에 모든 기능을
|
|
모으지 않는다.
|
|
|
|
## 16. CI Gate 설계
|
|
|
|
기존 gate taxonomy에 다음 검증을 명시적으로 포함한다.
|
|
|
|
### 16.1 Merge gate
|
|
|
|
- frozen install
|
|
- production typecheck
|
|
- test/e2e typecheck
|
|
- lint
|
|
- architecture
|
|
- runtime schema
|
|
- unit
|
|
- contract
|
|
- component
|
|
- integration
|
|
- coverage
|
|
- Storybook build
|
|
- Storybook interaction/a11y
|
|
- critical 3-engine E2E
|
|
- automated route a11y
|
|
- signed manual accessibility evidence for every registered route in scope
|
|
- design-system visual baseline
|
|
- production build
|
|
- sample removal
|
|
- security
|
|
|
|
### 16.2 Release gate
|
|
|
|
- bundle budget
|
|
- built-dist 3-engine smoke
|
|
- base path/deep-link
|
|
- release/config coherence
|
|
- chunk mismatch recovery
|
|
- hosting header
|
|
- lab performance
|
|
- release visual smoke
|
|
|
|
### 16.3 Production/field gate
|
|
|
|
- rollback/runbook drill
|
|
- provider smoke
|
|
- approved production Web Vitals evidence
|
|
|
|
외부 배포 주소나 28일 field evidence가 없는 skeleton 개발 단계에서는 해당 gate가
|
|
실제 프로젝트가 채울 계약으로 남는다. 이를 fake data로 통과시키지 않는다.
|
|
현재 `FE-GATE-009`의 signed manual accessibility review는 merge gate다. 이
|
|
문서는 이를 production/field gate로 이동시키지 않는다.
|
|
|
|
### 16.4 병렬화와 shard
|
|
|
|
- unit/contract/component는 안정된 shard key를 사용한다.
|
|
- Playwright는 project와 shard를 함께 증거 이름에 포함한다.
|
|
- shard 하나가 실패해도 나머지 증거를 업로드한다.
|
|
- 최종 gate는 모든 shard의 통과를 AND로 계산한다.
|
|
- test order 의존성을 찾기 위해 정기적으로 순서를 섞을 수 있으나 random seed를
|
|
증거에 기록한다.
|
|
|
|
### 16.5 Flaky policy
|
|
|
|
- merge를 통과시키기 위한 임의 `test.skip` 금지
|
|
- quarantine에는 owner, defect ID, 영향 route, 만료일 필요
|
|
- retry 후 pass도 성공으로만 집계하지 않고 flaky report에 기록
|
|
- 동일 test가 반복되면 clock/network/focus/shared-state 원인을 제거
|
|
- CI machine 성능에 맞추기 위해 assertion을 제거하거나 timeout만 계속 늘리지
|
|
않는다.
|
|
|
|
## 17. 새 Feature 테스트 Recipe
|
|
|
|
도메인 feature를 추가할 때 다음 순서를 따른다.
|
|
|
|
각 단계는 feature capability에 해당할 때 적용한다. 적용되지 않는 query,
|
|
mutation, form, visual, native-browser 항목은 조용히 생략하지 않고 테스트 계획에
|
|
`N/A`와 이유를 기록한다. 예를 들어 read-only static route에 422, optimistic
|
|
rollback, dirty form test를 만들지 않는다.
|
|
|
|
### Step 1. 위험과 계약을 열거한다
|
|
|
|
- 사용자가 완료하려는 action은 무엇인가?
|
|
- 데이터 손실, 중복 mutation, 권한 노출 위험은 무엇인가?
|
|
- URL, API operation, query key, telemetry event는 무엇인가?
|
|
- loading/empty/stale/error/conflict/access 상태는 무엇인가?
|
|
- keyboard, mobile, locale 요구는 무엇인가?
|
|
|
|
위험마다 가장 가까운 테스트 계층을 지정한다.
|
|
|
|
### Step 2. Type과 schema를 먼저 닫는다
|
|
|
|
- route params/search
|
|
- command/query
|
|
- API request/response
|
|
- domain/application result
|
|
- view model
|
|
- form value
|
|
- query key
|
|
|
|
positive와 negative type/schema fixture를 추가한다.
|
|
|
|
### Step 3. MSW scenario를 등록한다
|
|
|
|
feature operation에 필요한 scenario ID를 catalog에 추가한다.
|
|
|
|
최소:
|
|
|
|
- success
|
|
- empty 또는 not-found
|
|
- slow
|
|
- auth/forbidden
|
|
- schema mismatch
|
|
- retryable/terminal failure
|
|
- mutation이면 409/422
|
|
|
|
기존 공통 scenario로 충분하면 새 이름을 만들지 않는다.
|
|
|
|
### Step 4. Pure policy와 mapper를 unit test한다
|
|
|
|
- normalization
|
|
- mapping
|
|
- policy
|
|
- error classification
|
|
- query key
|
|
- optimistic update를 사용하는 mutation의 patch/rollback
|
|
|
|
DOM이나 MSW를 불필요하게 사용하지 않는다.
|
|
|
|
### Step 5. Query/mutation integration을 작성한다
|
|
|
|
- 실제 local query adapter
|
|
- 새 QueryClient
|
|
- 실제 application use case
|
|
- MSW
|
|
- query cancellation/retry, mutation invalidation, 적용되는 경우 optimistic
|
|
rollback
|
|
|
|
HTTP client를 `vi.fn()` 결과로 대체해 boundary contract를 건너뛰지 않는다.
|
|
|
|
### Step 6. Form이 있는 경우 form/controller를 작성한다
|
|
|
|
- field semantics
|
|
- client validation
|
|
- 422 mapping
|
|
- submit pending
|
|
- conflict
|
|
- dirty/reset
|
|
|
|
### Step 7. Page component를 검증한다
|
|
|
|
- page template
|
|
- initial/loading/empty/success/error
|
|
- actions
|
|
- route params/search
|
|
- heading focus
|
|
- access state
|
|
|
|
role과 accessible name으로 상호작용한다.
|
|
|
|
### Step 8. Story를 추가한다
|
|
|
|
- 모든 주요 state
|
|
- dark
|
|
- compact
|
|
- long/pseudo locale
|
|
- interaction
|
|
- a11y
|
|
|
|
### Step 9. E2E critical flow를 추가한다
|
|
|
|
- direct route
|
|
- keyboard flow
|
|
- success
|
|
- 사용자 복구 가능한 failure
|
|
- mobile
|
|
- 3-engine이 필요한 native/focus behavior
|
|
|
|
implementation detail가 아니라 사용자 결과를 assertion한다.
|
|
|
|
### Step 10. Visual baseline을 추가한다
|
|
|
|
새 layout, template variant, 상태 표면이 생긴 경우만 baseline을 추가한다. 기존
|
|
primitive를 단순히 조합한 모든 feature page를 무조건 screenshot으로 고정하지
|
|
않는다.
|
|
|
|
### Step 11. Evidence와 문서를 갱신한다
|
|
|
|
- test taxonomy
|
|
- route/accessibility scope
|
|
- CI gate evidence
|
|
- feature recipe
|
|
- 운영 failure/runbook 연결
|
|
|
|
### Step 12. 전체 gate를 실행한다
|
|
|
|
```bash
|
|
corepack pnpm check:types
|
|
corepack pnpm lint
|
|
corepack pnpm check:architecture
|
|
corepack pnpm test:runtime-schema
|
|
corepack pnpm test:unit
|
|
corepack pnpm test:component
|
|
corepack pnpm test:integration
|
|
corepack pnpm test:e2e
|
|
corepack pnpm test:a11y
|
|
corepack pnpm build
|
|
corepack pnpm check:bundle
|
|
```
|
|
|
|
TypeScript test, Storybook, coverage, visual, built-dist 명령이 도입되면 위 목록과
|
|
CI registry에 추가한다.
|
|
|
|
## 18. Feature Definition of Done
|
|
|
|
아래 체크는 feature에 존재하는 capability에 적용한다. `N/A`에는 이유와 해당
|
|
위험을 대신 검증하는 계층을 기록한다. 공통 type, architecture, bootstrap,
|
|
접근성, security gate는 임의로 `N/A` 처리할 수 없다.
|
|
|
|
### Type과 계약
|
|
|
|
- [ ] production source와 test source typecheck가 통과한다.
|
|
- [ ] route/API/query/form type이 closed contract다.
|
|
- [ ] positive와 필요한 negative schema fixture가 있다.
|
|
- [ ] registry와 runtime mapping이 exhaustive하다.
|
|
|
|
### Unit과 integration
|
|
|
|
- [ ] pure policy와 mapper의 모든 중요 branch를 검증했다.
|
|
- [ ] MSW shared scenario를 사용한다.
|
|
- [ ] unhandled request가 test를 실패시킨다.
|
|
- [ ] query가 있으면 cancellation, retry, stale 상태를 검증했다.
|
|
- [ ] mutation이 있으면 invalidation/conflict를, optimistic update를 쓰면
|
|
rollback을 검증했다.
|
|
- [ ] auth recovery가 bounded임을 검증했다.
|
|
|
|
### Form과 UI
|
|
|
|
- [ ] form이 있으면 field label/description/error가 연결된다.
|
|
- [ ] form이 있으면 error summary와 first-invalid focus가 동작한다.
|
|
- [ ] mutation form이 있으면 pending과 double submit을 검증했다.
|
|
- [ ] loading/empty/success/error/access 상태가 있다.
|
|
- [ ] keyboard와 focus test가 있다.
|
|
- [ ] component/story a11y가 통과한다.
|
|
|
|
### Router와 bootstrap
|
|
|
|
- [ ] deep link와 browser navigation이 동작한다.
|
|
- [ ] params/search invalid 입력이 안전하게 처리된다.
|
|
- [ ] route error를 검증하고 lazy chunk가 있는 경우 recovery를 검증했다.
|
|
- [ ] 실제 bootstrap/provider composition에서 feature가 동작한다.
|
|
- [ ] built-dist smoke가 통과한다.
|
|
|
|
### Browser와 visual
|
|
|
|
- [ ] native/focus/browser 차이가 있는 critical flow가 지정된 3개 엔진에서
|
|
통과하고, 나머지는 browser matrix 정책을 따른다.
|
|
- [ ] 320px와 대표 mobile viewport에서 overflow가 없다.
|
|
- [ ] 필요한 light/dark visual baseline이 있다.
|
|
- [ ] pseudo-locale/긴 문구가 잘리지 않는다.
|
|
- [ ] failure trace와 report가 CI evidence로 남는다.
|
|
|
|
### Coverage와 운영
|
|
|
|
- [ ] high-risk module branch 목표를 충족한다.
|
|
- [ ] 신규 코드의 미검증 branch에 승인 없는 예외가 없다.
|
|
- [ ] bundle/performance budget을 통과한다.
|
|
- [ ] telemetry/error output에 민감 정보가 없다.
|
|
- [ ] 관련 gate와 evidence registry가 갱신되었다.
|
|
|
|
## 19. 금지 패턴
|
|
|
|
- production bootstrap을 복사한 test-only shell만 검증
|
|
- query cache singleton을 test 사이에 공유
|
|
- `fetch`나 HTTP client 전체를 `vi.fn()`으로 대체하고 MSW integration 생략
|
|
- 성공 응답만 제공하는 feature mock
|
|
- test마다 서로 다른 401/422/500 body를 임의 작성
|
|
- test에서 실제 sleep 사용
|
|
- timeout을 늘려 race condition 숨김
|
|
- implementation class와 DOM nesting만 assertion
|
|
- 대형 DOM snapshot으로 behavior test 대체
|
|
- failure screenshot을 visual regression이라고 부름
|
|
- dev server E2E만으로 release build 완료 선언
|
|
- Chromium만 통과하고 native focus/browser 호환 완료 선언
|
|
- axe 결과만으로 접근성 완료 선언
|
|
- global coverage 숫자로 고위험 branch 누락 은폐
|
|
- type error를 `any`, `@ts-ignore`, 이중 cast로 숨김
|
|
- production bundle에 MSW worker, mock selector, fake credential 포함
|
|
- flaky test를 owner/만료일 없이 skip
|
|
- CI gate에 `continue-on-error`
|
|
|
|
## 20. 단계별 도입 순서
|
|
|
|
1. `tsconfig.test.json`과 test typecheck gate를 추가한다.
|
|
2. production bootstrap을 주입 가능한 함수로 추출하고 integration test를 만든다.
|
|
3. MSW handlers/scenario/factory를 중앙 catalog로 이동한다.
|
|
4. query/mutation presentation adapter와 integration harness를 만든다.
|
|
5. form foundation과 form/controller test matrix를 만든다.
|
|
6. route registry/runtime map contract와 built-dist E2E를 추가한다.
|
|
7. Storybook build, interaction, a11y gate를 추가한다.
|
|
8. pinned Chromium visual baseline을 추가한다.
|
|
9. critical flow의 3-engine release profile을 분리한다.
|
|
10. V8 coverage와 high-risk/diff policy를 차단 gate로 추가한다.
|
|
11. feature recipe와 Definition of Done를 pull request template에 연결한다.
|
|
|
|
이 순서의 완료 기준은 테스트 도구를 모두 설치하는 것이 아니다. 새 도메인
|
|
기능이 추가되었을 때 개발자가 성공·실패·권한·복구·접근성·브라우저·release
|
|
위험을 어디서 어떻게 검증할지 추가 설계 없이 결정할 수 있어야 한다.
|