Node.js test runner coverage ignore 가이드: 의도적으로 제외할 코드를 주석으로 관리하는 법
커버리지 리포트는 테스트 품질을 보는 좋은 신호지만, 모든 줄을 같은 무게로 봐야 하는 것은 아닙니다. 운영에서는 플랫폼별 분기, 방어적 fallback, 실제로 실행하기 어려운 종료 처리처럼 테스트로 검증하기보다 코드 리뷰와 별도 점검이 더 적합한 코드가 있습니다. 이런 코드를 아무 설명 없이 커버리지 기준에서 제외하면 숫자는 좋아져도 신뢰는 떨어집니다.
Node.js 내장 test runner는 --experimental-test-coverage로 커버리지를 수집할 때 node:coverage 주석을 인식합니다.
특정 줄이나 범위를 제외할 수 있으므로, 커버리지 예외를 별도 설정 파일이 아니라 코드 가까이에 남길 수 있습니다.
중요한 것은 “테스트하기 귀찮은 코드”를 숨기는 용도가 아니라, 의도와 범위가 분명한 예외만 작게 표시하는 것입니다.
이 글에서는 Node.js test runner의 coverage ignore 주석을 어떻게 쓰고, CI 커버리지 기준과 리뷰 규칙에 어떻게 연결하면 좋은지 정리합니다. 기본 커버리지 실행은 Node.js test runner coverage V8 가이드, include/exclude 정책은 Node.js test runner coverage include/exclude 가이드, CI 임계값 운영은 Node.js test runner coverage threshold 가이드와 함께 보면 좋습니다.
참고 문서: Node.js test runner 공식 문서
coverage ignore가 필요한 이유
커버리지 숫자와 테스트 신뢰는 다르다
커버리지 100%는 모든 동작이 안전하다는 뜻이 아닙니다. 반대로 커버리지 92%가 항상 나쁜 것도 아닙니다. 테스트가 닿기 어려운 코드가 명확히 분리되어 있고, 그 이유가 리뷰 가능한 형태로 남아 있다면 숫자보다 해석이 중요합니다.
예를 들어 아래 코드는 운영 환경에서만 실행되는 종료 처리입니다. 테스트에서 실제 프로세스를 종료시키는 방식으로 검증하면 테스트 자체가 불안정해질 수 있습니다.
export function installFatalErrorHandler(logger) {
process.on('uncaughtException', (error) => {
logger.error({ error }, 'fatal exception');
/* node:coverage ignore next */
process.exit(1);
});
}
이 경우 logger.error()까지는 테스트하고, 실제 process.exit(1) 호출은 별도의 통합 테스트나 운영 런북 점검 대상으로 남길 수 있습니다.
주석은 제외 이유를 완전히 설명하지 않으므로, 주변 코드나 테스트 이름에서 의도가 드러나야 합니다.
예외는 작을수록 안전하다
커버리지 ignore는 넓게 쓰기 시작하면 금방 의미가 흐려집니다. 함수 전체나 파일 전체를 제외하기보다, 테스트가 비현실적인 한 줄이나 짧은 분기만 대상으로 삼는 편이 안전합니다.
나쁜 신호는 아래와 같습니다.
- 새 기능 코드 전체가 ignore 처리되어 있다.
- 실패하기 쉬운 분기를 테스트 대신 ignore로 덮었다.
- ignore 주석 주변에 이유를 추론할 단서가 없다.
- 커버리지 임계값을 맞추기 위해 예외가 계속 늘어난다.
좋은 기준은 간단합니다. 리뷰어가 “이 줄은 왜 테스트하지 않았는가?”라고 물었을 때, 코드와 테스트만 보고도 답을 납득할 수 있어야 합니다.
node:coverage 주석 기본 문법
한 줄만 제외하려면 ignore next를 쓴다
공식 문서 기준으로 Node.js test runner는 다음 줄을 제외하는 /* node:coverage ignore next */ 주석을 지원합니다.
가장 흔하고 안전한 사용 방식입니다.
export function getRuntimeLabel() {
if (process.platform === 'win32') {
return 'windows';
}
/* node:coverage ignore next */
if (process.platform === 'aix') {
return 'aix';
}
return 'posix';
}
이 예제에서 aix 분기는 로컬 개발과 일반 CI에서 재현하기 어렵습니다.
다만 실제 서비스가 AIX를 지원하지 않는다면 ignore가 아니라 분기 자체를 제거하는 편이 낫습니다.
coverage ignore는 지원해야 하지만 일반 테스트 환경에서 실행하기 어려운 코드에만 쓰는 것이 좋습니다.
여러 줄은 개수를 명시한다
짧은 블록을 제외해야 한다면 ignore next 3처럼 줄 수를 붙일 수 있습니다.
이때 범위가 눈에 보이도록 작게 유지하는 것이 핵심입니다.
export function createOptionalNativeAdapter() {
try {
return require('./build/Release/native-adapter.node');
} catch (error) {
/* node:coverage ignore next 3 */
if (error.code === 'MODULE_NOT_FOUND') {
return null;
}
throw error;
}
}
위 코드는 optional native 모듈이 없을 때의 fallback만 제외합니다.
하지만 throw error는 그대로 커버리지 대상에 남습니다.
예외 범위를 작게 잡으면 ignore가 실제 오류 경로를 가리지 않습니다.
범위 제외는 신중하게 쓴다
disable과 enable은 반드시 짝을 맞춘다
Node.js 문서에는 /* node:coverage disable */과 /* node:coverage enable */로 여러 줄을 제외하는 방식도 소개되어 있습니다.
이 방식은 편리하지만, 범위를 실수로 넓게 잡기 쉽습니다.
export function printDebugBanner(stream) {
/* node:coverage disable */
if (process.env.DEBUG_BANNER === '1') {
stream.write('debug mode enabled\n');
stream.write(`pid=${process.pid}\n`);
}
/* node:coverage enable */
}
disable을 썼다면 같은 화면 안에서 enable이 보일 정도로 짧게 유지하세요.
파일 아래쪽까지 길게 이어지는 disable 블록은 리뷰 난이도를 높입니다.
긴 블록이 필요해 보인다면 테스트 대상 설계가 잘못됐거나, 파일 단위 include/exclude 정책으로 분리하는 편이 나을 수 있습니다.
생성 코드와 애플리케이션 코드는 다르게 관리한다
커버리지 제외가 필요한 대표 사례 중 하나는 생성 코드입니다. 예를 들어 빌드 과정에서 만들어지는 라우트 매니페스트나 타입 변환 결과는 사람이 직접 테스트를 작성하기 애매할 수 있습니다. 하지만 이런 파일에는 주석을 붙이기보다 커버리지 include/exclude 정책으로 관리하는 편이 낫습니다.
애플리케이션 소스 안에서의 ignore는 “이 코드가 왜 예외인가”를 설명하는 신호여야 합니다. 생성물, vendor 코드, 빌드 산출물처럼 파일 성격 자체가 다른 경우에는 test runner의 coverage include/exclude 옵션으로 경계를 나누는 것이 더 선명합니다.
CI에서 운영하는 기준
ignore 주석 수를 리뷰 대상으로 둔다
커버리지 예외는 시간이 지나면서 늘어나기 쉽습니다. 따라서 CI 숫자만 보는 대신, PR 리뷰에서 ignore 주석의 증가를 확인하는 규칙을 두면 좋습니다.
간단한 점검 스크립트는 아래처럼 만들 수 있습니다.
// scripts/check-coverage-ignore.mjs
import { readdir, readFile } from 'node:fs/promises';
import { join } from 'node:path';
const sourceRoot = new URL('../src/', import.meta.url);
const marker = 'node:coverage';
let count = 0;
async function walk(dir) {
for (const entry of await readdir(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isDirectory()) {
await walk(path);
continue;
}
if (!entry.name.endsWith('.js') && !entry.name.endsWith('.mjs')) {
continue;
}
const source = await readFile(path, 'utf8');
count += source.split(marker).length - 1;
}
}
await walk(sourceRoot);
if (count > 8) {
throw new Error(`Too many coverage ignore comments: ${count}`);
}
console.log(`coverage ignore comments: ${count}`);
이 숫자는 절대 기준이 아니라 팀의 합의입니다. 초기에는 실패시키지 않고 리포트만 남긴 뒤, 예외가 안정되면 임계값을 걸어도 됩니다.
커버리지 임계값을 낮추는 대신 예외 이유를 남긴다
CI에서 커버리지 기준이 자주 깨진다면 두 가지 선택지가 있습니다. 하나는 임계값을 낮추는 것이고, 다른 하나는 테스트 대상과 예외 대상을 다시 분리하는 것입니다.
임계값을 낮추는 것은 빠르지만, 원인을 가립니다.
반면 node:coverage 주석은 예외 위치가 코드에 남습니다.
리뷰어가 실제로 제외된 줄을 볼 수 있고, 나중에 테스트 가능해졌을 때 제거하기도 쉽습니다.
권장 흐름은 아래와 같습니다.
- 먼저 테스트로 검증할 수 있는지 확인한다.
- 테스트가 불안정하거나 환경 의존적이라면 ignore 범위를 최소화한다.
- ignore 주변 코드가 왜 예외인지 드러나게 이름과 구조를 정리한다.
- PR에서 ignore 추가 이유를 짧게 남긴다.
- 정기적으로 오래된 ignore 주석을 제거할 수 있는지 확인한다.
실무 체크리스트
추가해도 되는 경우
- OS, 런타임, CPU 아키텍처처럼 일반 CI에서 재현하기 어려운 분기
- 실제 프로세스 종료나 fatal path처럼 테스트가 위험해질 수 있는 한 줄
- 선택적 native 모듈 fallback처럼 별도 환경에서 검증하는 코드
- 방어적 코드 중 테스트 fixture로 억지 재현하면 오히려 테스트 의미가 흐려지는 코드
피해야 하는 경우
- 아직 테스트를 쓰지 않은 신규 비즈니스 로직
- 버그가 자주 나는 에러 처리 경로
- 외부 입력 검증 코드
- 인증, 권한, 결제, 개인정보 처리처럼 리스크가 큰 코드
- 커버리지 임계값을 맞추기 위한 넓은 범위 제외
특히 보안 경계에 가까운 코드는 ignore보다 테스트를 우선해야 합니다. 입력 검증, 권한 체크, 토큰 처리, 파일 경로 검증 같은 코드는 “드물게 실행된다”는 이유만으로 제외하면 안 됩니다.
FAQ
node:coverage ignore 주석은 모든 커버리지 도구에서 동작하나요?
아닙니다.
이 글은 Node.js 내장 test runner의 --experimental-test-coverage 흐름을 기준으로 설명합니다.
다른 커버리지 도구를 함께 쓰고 있다면 해당 도구가 같은 주석을 인식하는지 별도로 확인해야 합니다.
disable 블록을 파일 상단에 걸어도 되나요?
가능하더라도 권장하지 않습니다.
파일 전체가 테스트 대상이 아니라면 coverage include/exclude 정책으로 빼는 편이 명확합니다.
애플리케이션 코드 안의 disable은 짧은 범위에만 사용하세요.
ignore 주석이 많아지면 어떻게 정리해야 하나요?
먼저 ignore를 유형별로 나누세요. 환경 의존 분기, 종료 처리, optional dependency fallback, 생성 코드가 섞여 있다면 각각 다른 해법이 필요합니다. 생성 코드는 exclude 정책으로 옮기고, 비즈니스 로직 예외는 테스트를 추가하는 쪽으로 줄이는 것이 좋습니다.
마무리
node:coverage 주석은 커버리지 숫자를 예쁘게 만드는 장치가 아니라, 테스트하지 않을 코드를 명시적으로 드러내는 장치입니다.
한 줄 제외는 ignore next, 짧은 범위 제외는 disable과 enable을 쓰되, 범위와 이유를 작고 선명하게 유지해야 합니다.
CI에서는 커버리지 임계값만 보지 말고 ignore 주석의 증가도 함께 확인하세요. 예외가 코드 가까이에 남아 있으면 팀은 숫자 뒤에 숨은 판단을 리뷰할 수 있고, 나중에 테스트 가능성이 생겼을 때 더 쉽게 되돌릴 수 있습니다.
이 글은 AI가 작성/정리하고, 오너가 방향을 결정한 프로젝트 운영 로그입니다.