1편에서 테스트의 4가지 유형을 살펴봤다. 이번 편에서는 그중 가장 진입 장벽이 낮은 정적 테스트를 다룬다. Next.js + TypeScript 프로젝트를 하고 있다면, 사실 이미 정적 테스트를 하고 있는 셈이다. 다만 제대로 활용하고 있느냐가 문제다.
정적 테스트는 코드를 실행하지 않고 코드 자체를 분석해서 문제를 찾는다. 실행 전에 잡을 수 있는 버그는 미리 잡자는 것이 핵심 철학이다.
TypeScript 엄격 모드
TypeScript는 기본 설정만으로도 많은 오류를 잡아주지만, 엄격 모드를 켜면 훨씬 강력해진다. tsconfig.json에서 strict 옵션 하나만 켜면 된다.
{
"compilerOptions": {
"strict": true
}
}
이 한 줄이 내부적으로 여러 옵션을 한꺼번에 활성화한다. 그중 실무에서 체감이 큰 것들을 살펴보자.
strictNullChecks는 null과 undefined를 명시적으로 처리하도록 강제한다. 이 옵션이 꺼져 있으면 아래 코드가 아무 경고 없이 통과한다.
function getUser(id: string) {
// DB에서 못 찾으면 undefined를 반환할 수 있음
return users.find(u => u.id === id);
}
// strictNullChecks가 꺼져 있으면 경고 없음
// 켜져 있으면 "undefined일 수 있다"고 알려줌
const user = getUser("123");
console.log(user.name); // 런타임에 터질 수 있음
strictNullChecks가 켜져 있으면 TypeScript가 “user가 undefined일 수 있으니 먼저 확인해라”고 알려준다. 런타임에 터질 버그를 코드 작성 시점에 잡는 것이다.
noImplicitAny는 타입을 명시하지 않아서 any로 추론되는 경우를 막는다. any가 많아지면 TypeScript를 쓰는 의미가 사라지기 때문에, 이 옵션은 반드시 켜두는 것이 좋다.
이미 프로젝트가 진행 중이라 strict를 한 번에 켜기 어렵다면, 위 옵션들을 하나씩 켜가며 점진적으로 적용해도 된다.
ESLint — 코드 규칙 자동 검사
TypeScript가 타입 오류를 잡는다면, ESLint는 코드 품질과 일관성을 잡는다. 선언하고 안 쓰는 변수, 잘못된 import 순서, React Hook 규칙 위반 같은 것들이다.
Next.js는 ESLint가 기본 내장되어 있어서 별도 설치 없이 바로 사용할 수 있다.
npx next lint
이 명령어를 처음 실행하면 ESLint 설정을 어떻게 할지 물어본다. “Strict” 옵션을 선택하면 Next.js 권장 규칙이 모두 적용된다.
추가로 자주 쓰이는 플러그인을 소개한다.
eslint-plugin-import는 import 순서를 자동 정리하고, 존재하지 않는 모듈을 import하는 실수를 잡아준다. eslint-plugin-unused-imports는 사용하지 않는 import를 자동으로 제거해준다. 작은 것 같지만 코드가 깔끔해지는 데 큰 도움이 된다.
// .eslintrc.js (예시)
module.exports = {
extends: ["next/core-web-vitals", "next/typescript"],
rules: {
"no-console": "warn",
"no-unused-vars": "error",
},
};
no-console을 warn으로 설정하면 console.log를 남겨둔 곳을 경고로 알려준다. 디버깅용 로그가 프로덕션에 배포되는 것을 방지할 수 있다.
Prettier — 코드 포맷 자동 정리
Prettier는 코드의 동작과는 관계없지만, 팀 전체가 동일한 코드 스타일을 유지하도록 강제한다. 탭 vs 스페이스, 세미콜론 유무, 따옴표 종류 같은 논쟁을 Prettier에게 맡기고 개발에 집중할 수 있다.
npm install -D prettier eslint-config-prettier
eslint-config-prettier는 ESLint와 Prettier가 충돌하는 규칙을 자동으로 꺼준다. 이걸 안 넣으면 ESLint가 고치라는 것과 Prettier가 고치라는 것이 서로 싸우는 상황이 벌어진다.
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 80
}
설정 내용은 팀 취향에 따라 정하면 된다. 중요한 건 어떤 규칙이냐가 아니라 팀 전체가 같은 규칙을 쓰느냐다.
저장할 때 자동으로 돌리기
도구를 설치했어도 매번 수동으로 실행하면 결국 안 하게 된다. 파일을 저장하는 순간 자동으로 검사하고 정리되도록 설정하는 것이 핵심이다.
VS Code를 쓴다면 .vscode/settings.json에 아래 설정을 추가한다.
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
이 설정을 프로젝트에 포함시키면 팀원 모두가 동일한 자동 포맷팅 환경을 갖게 된다. 개인 설정이 아니라 프로젝트 설정으로 관리하는 것이 포인트다.
git commit 시 자동 검사 (husky + lint-staged)
저장 시 자동 포맷팅을 설정했더라도, 모든 팀원이 VS Code를 쓰는 것은 아니고, 설정을 빼먹을 수도 있다. 마지막 안전장치로 git commit 시점에 자동 검사를 걸 수 있다.
npm install -D husky lint-staged
npx husky init
.husky/pre-commit 파일을 아래처럼 수정한다.
npx lint-staged
package.json에 lint-staged 설정을 추가한다.
{
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix",
"prettier --write"
]
}
}
이렇게 하면 commit할 때 변경된 파일에 대해서만 ESLint와 Prettier가 자동으로 돌아간다. 규칙을 위반한 코드는 commit 자체가 막히기 때문에, 문제 있는 코드가 저장소에 올라가는 것을 원천 차단할 수 있다.
CI에서 한 번 더 검증
로컬에서 아무리 잘 막아도, 누군가 --no-verify 옵션으로 husky를 우회할 수 있다. CI(GitHub Actions 등)에서 한 번 더 검증하면 완벽하다. 이 부분은 6편에서 자세히 다루지만, 핵심만 미리 보면 이렇다.
{
"scripts": {
"lint": "next lint",
"type-check": "tsc --noEmit"
}
}
CI에서 npm run lint와 npm run type-check를 실행하도록 설정하면, 정적 테스트가 3중으로 보호된다. 저장 시(IDE), commit 시(husky), push 시(CI).
정적 테스트만으로 잡을 수 없는 것
정적 테스트는 강력하지만 한계가 명확하다. 코드가 문법적으로 올바른지, 타입이 맞는지는 확인할 수 있지만 “실제로 의도한 대로 동작하는가”는 알 수 없다.
예를 들어 아래 코드는 정적 테스트를 완벽히 통과한다.
function calcTax(price: number): number {
return price * 0.2; // 10%여야 하는데 20%로 잘못 작성
}
타입도 맞고, 문법도 맞고, ESLint 규칙도 위반하지 않는다. 하지만 비즈니스 로직이 틀렸다. 이런 문제를 잡으려면 다음 단계인 단위 테스트가 필요하다.
다음 편 예고
정적 테스트로 코드 품질의 기본 바닥을 깔았다. 다음 편에서는 Vitest를 설치하고 첫 번째 단위 테스트를 직접 작성해본다. 함수 하나, 컴포넌트 하나를 테스트하는 것부터 시작한다.