blog

Next.js App Router E2E 커버리지와 istanbul ignore 주석 분석

Front-end / Testing#nextcov#Istanbul#V8 Coverage#Next.js#App Router#SWC

배경

Next.js App Router 프로젝트에서 E2E 커버리지를 운영하다 보니 istanbul ignore 주석이 160개 가까이 쌓였다. 이 주석들이 왜 생기는지, 줄일 수 있는지, 지금 구조가 이 스택에서 할 수 있는 최선인지 분석했다.

1. 수집 파이프라인

build:e2e
  → webpack 빌드 (전체 소스맵 + 압축 끔)

start:e2e
  → NODE_V8_COVERAGE=.v8-coverage   서버 쪽 V8 커버리지 파일 기록
  → node --inspect=9230              CDP 포트 개방

E2E 테스트 실행

finalize-coverage
  → 브라우저 V8 커버리지 + 서버 V8 커버리지 수집
  → 소스맵 매핑, V8 → Istanbul 형식 변환
  → html / lcov / json / text-summary 리포트
// next.config.ts - 커버리지 빌드에서만 적용
if (!dev && process.env.E2E_COVERAGE) {
  config.devtool = 'source-map';
  config.optimization = { ...config.optimization, minimize: false };
}

Istanbul 계측 vs V8 수집

항목Istanbul(빌드 계측)V8(nextcov)
계측 시점빌드 시 AST에 카운터 삽입런타임에 엔진이 실행 범위 기록
수집window.__coverage__에 누적CDP, NODE_V8_COVERAGE
형식 변환불필요V8 → Istanbul 변환 필요
원본 매핑계측 시점의 AST 기준빌드 결과물 + 소스맵에 의존

Istanbul은 원본 AST에 직접 카운터를 넣기 때문에 분기·함수 단위가 원본 코드와 정확히 맞는다. V8 방식은 빌드된 코드의 실행 범위를 소스맵으로 되짚어 가므로, 번들러 변환과 소스맵 품질에 따라 정밀도가 달라진다. (최근 V8 → Istanbul 변환 도구들은 AST 기반으로 정밀도를 많이 끌어올렸지만, 이 프로젝트에서는 아래 패턴에서 매핑 오차가 남았다.)

2. istanbul ignore 주석 분류

그룹사유개수
A소스맵 매핑 정밀도 한계 (인라인 JSX 콜백, async continuation)41
B수집 한계로 보이는 패턴 (useEffect 본문, JSX prop의 화살표 함수)21
C페이지 이탈 시 커버리지 소멸 (router.back() 직후 코드)4
D하드웨어 의존 (카메라 등 실기기 전용)12
E논리적으로 도달 불가 (UI나 Suspense 쿼리가 보장하는 방어 코드)82

A·B가 생기는 이유

onChange={(value) => doSomething(value)}   // 인라인 JSX 화살표 함수

useEffect(() => {
  (async () => {
    const result = await fetchData();
    setState(result);                         // await 이후 이어지는 코드
  })();
}, []);

이런 코드는 번들 단계에서 주변 코드와 함께 변환·인라인되어, 소스맵으로 되짚을 때 실행 범위가 원본의 정확한 위치에 대응되지 않는 경우가 있었다. 실제로 실행되는 것은 확인했으므로 "실행 확인됨" 사유와 함께 ignore 처리했다.

3. 빌드 시점 Istanbul 계측을 쓰지 못한 이유

A·B를 없애는 가장 확실한 방법은 빌드할 때 Istanbul로 계측하는 것이다. 하지만 App Router에서는 막혔다.

  1. Babel로 전환하면 Server Actions 빌드가 실패한다. babel-plugin-istanbul을 쓰려고 .babelrc를 추가하면 Next.js가 SWC 대신 Babel을 사용하는데, App Router의 Server Actions는 SWC 변환에 의존한다. (참고 글)
  2. SWC 계측 플러그인도 App Router에서 깨졌다. swc-plugin-coverage-instrument를 app 디렉터리와 함께 쓰면 동작하지 않는다는 이슈가 있다. (vercel/next.js #42580)
  3. .babelrc 없이 webpack 설정에서 babel-loader를 enforce: 'post'로 끼워 넣는 우회책은 이론상 가능하지만, App Router에서 검증된 사례를 찾지 못했다.

그래서 이 스택에서는 V8 기반 수집이 현실적인 선택이었다.

4. 결론

항목판단
nextcov(V8)유지. App Router + Server Actions에서 쓸 수 있는 방법
A·B (62개)유지. 실행은 확인했고 도구 한계를 사유와 함께 기록
C (4개)유지. 페이지 이탈 후 수집 불가
D (12개)유지. 실기기 전용 기능
E (82개)개별 재검토 대상
빌드 시점 Istanbul현재 불가

E 그룹은 다시 볼 여지가 있다. "정말 도달할 수 없는 코드"라면 커버리지에서 제외하기보다, 타입으로 불가능한 상태를 막거나 방어 코드를 지우는 편이 나을 수 있다. 반대로 API 응답 이상처럼 실제로 일어날 수 있는 방어 코드라면 모킹으로 E2E나 단위 테스트에서 검증하는 것이 맞다. ignore 주석은 사유를 반드시 함께 적어, 나중에 도구가 개선되면 다시 걷어낼 수 있게 한다.

참고