“테스트는 나중에…”를 끝내는 버튼 하나: /create-jest로 변경 감지→테스트 생성→실행까지 자동화하기

LinkDropper·2025년 12월 30일

Others

목록 보기
7/9
post-thumbnail

시리즈 안내

순서제목내용
1편Claude Code Commands로 개발 워크플로우 자동화하기전체 개요
2편/spec - 명세서 기반 코드 구현 자동화가장 핵심인 구현 자동화
3편/create-jest - 테스트 코드 자동 생성테스트 작성 자동화 (현재 글)
4편/w-context - AI를 위한 문서 작성CONTEXT.md 개념과 활용
5편/pr + /apply-review - PR 워크플로우 자동화PR 생성부터 리뷰 반영까지

들어가며

테스트 코드, 좋은 건 알아요.
근데 현실은 대개 이렇죠.

  • “이거 테스트는… 나중에 쓰자” (안 씀)
  • “급하니까 일단 배포하고…” (영원히 안 씀)
  • “기존 테스트 깨졌네? 일단 스킵…” (테스트의 죽음)

저도 똑같았습니다. 테스트가 필요 없는 게 아니라, 테스트를 쓰는 데 드는 에너지가 문제였어요.

그래서 만든 게 /create-jest입니다.

“코드 바꾸면 → 테스트가 따라온다”
라는 규칙을 고민이 아니라 시스템으로 강제해버린 명령어.


/create-jest가 하는 일 (진짜로는 “테스트 파이프라인”)

실행은 한 줄입니다.

/create-jest

하지만 내부적으로는 아래를 순서대로 처리해요.

  1. 변경된 파일 감지git diff로 무엇이 바뀌었는지 확인
  2. 테스트 대상 필터링 — “테스트할 파일”만 규칙으로 추려냄
  3. 테스트 생성/업데이트 — 있으면 수정, 없으면 새로 생성
  4. 테스트 실행 — 작성한 테스트가 실제로 통과하는지 확인

즉, “테스트 파일 만들어드립니다”가 아니라

코드 수정 → /create-jest테스트까지 끝

이 흐름을 만드는 게 목표였습니다.


핵심 설계: 자동화의 성패는 “무엇을 테스트할지”가 결정한다

테스트 자동화를 시도할 때 제일 먼저 부딪히는 문제가 이거예요.

  • 다 테스트하려면 유지보수가 지옥
  • 너무 덜 테스트하면 의미가 없음

그래서 저는 “테스트할 것 / 안 할 것”을 명확히 분리했습니다.

✅ 테스트 대상 (효율 좋은 구역만)

- `libs/**/*.ts` - 공통 라이브러리
- `hooks/**/*.ts` - 커스텀 훅
- `utils/**/*.ts` - 유틸리티 함수
- `services/**/*.ts` - 서비스 레이어

이 영역들은 보통 순수 로직이 많고, 테스트 비용 대비 효과가 커요.
즉 “자동 생성”과 궁합이 좋습니다.

❌ 테스트 제외 (여긴 다른 전략이 낫다)

- `app/**/*` - 앱 라우터 (E2E로 커버)
- `components/**/*` - React 컴포넌트 (별도 컴포넌트 테스트)
- `**/*.d.ts` - 타입 정의 파일
- `**/index.ts` - re-export만 하는 파일
- `**/__tests__/**` - 테스트 파일 자체
- `*.config.*` - 설정 파일

UI/라우팅은 단위 테스트로 무리해서 커버하기보다, E2E나 컴포넌트 테스트 전략이 더 적합하다고 판단했어요.
그래서 /create-jest는 “로직 구역”만 확실히 쌓아주는 역할을 맡습니다.


테스트 파일 위치/네이밍: 흩어지지 않게 “한 규칙”으로

테스트가 자동으로 생기기 시작하면, 다음 문제가 옵니다.

“어디에 생겼더라?”
“왜 어떤 건 여기 있고 어떤 건 저기 있어?”

그래서 무조건 같은 규칙으로 생성하도록 했어요.

위치: 동일 경로의 __tests__ 폴더

libs/fetch/
├── core.ts
├── types.ts
├── index.ts
└── __tests__/
    └── fetch.test.ts

소스랑 같은 위치에 있으니 찾기 쉽고, 모듈 단위로 묶여서 관리도 편합니다.

네이밍: “모듈 크기” 기준으로 실용적으로

모듈 크기파일 수네이밍예시
작음1~3개{모듈명}.test.tsfetch.test.ts
4개 이상{파일명}.test.tssession.test.ts

작은 모듈은 통합 테스트 파일 하나가 관리가 쉽고,
큰 모듈은 파일 단위로 쪼개야 유지보수가 됩니다.


테스트 작성 규칙: “팀 컨벤션”을 명령어에 박아두기

여기가 /create-jest의 진짜 핵심이에요.

테스트를 못 쓰는 이유는 보통 “시간”보다도
매번 선택해야 하는 것들이 너무 많아서예요.

  • describe 어떻게 나누지?
  • 케이스는 뭐부터 쓰지?
  • 모킹은 어디까지 하지?
  • 테스트명은 영어로? 한글로?

그래서 저는 그 선택을 명령어가 대신 하도록 만들었습니다.


1) 한글 테스트명 (실패 로그가 곧 문서가 된다)

it("사용자를 생성한다", () => {});
it("유효하지 않은 이메일이면 에러를 던진다", () => {});

한글 테스트명의 장점은 단순해요.

  • 실패했을 때 로그만 봐도 “뭐가 깨졌는지” 바로 이해됨
  • 팀원이 테스트 파일을 읽을 때도 맥락이 빨리 잡힘

테스트는 “작성자”보다 “미래의 유지보수자”를 위한 거라서, 저는 한글이 더 실용적이었습니다.


2) AAA 패턴 강제 (테스트 구조를 자동으로 통일)

it("정상 케이스를 처리한다", () => {
  // Arrange
  const input = { name: "홍길동", email: "hong@test.com" };

  // Act
  const result = createUser(input);

  // Assert
  expect(result).toEqual({ id: expect.any(String), ...input });
});

AAA(Arrange-Act-Assert)를 강제하면 좋은 점:

  • 테스트가 길어져도 읽기 쉬움
  • “준비/실행/검증”이 섞이지 않아서 수정하기 쉬움
  • 팀원이 테스트 스타일로 싸울 일이 사라짐

3) describe로 기능 단위 그룹화 (실패 위치를 즉시 좁힌다)

describe("UserService", () => {
  describe("createUser", () => {
    it("정상 케이스를 처리한다", () => {});
    it("중복 이메일이면 에러를 던진다", () => {});
  });

  describe("updateUser", () => {
    it("존재하는 사용자를 업데이트한다", () => {});
    it("존재하지 않는 사용자면 에러를 던진다", () => {});
  });
});

이 구조는 테스트가 많아질수록 효과가 커집니다.
특히 CI에서 실패했을 때 “어느 기능이 문제인지”가 바로 찍혀요.


테스트 케이스 우선순위: 중요한 것부터 자동으로 채운다

Claude가 테스트를 생성할 때 저는 우선순위를 고정했습니다.

  1. Happy Path — 정상 입력/정상 결과
  2. Edge Cases — 경계값, 빈 값, null/undefined
  3. Error Cases — 예외 상황, 에러 핸들링
  4. Integration (필요시) — 모듈 간 상호작용

이렇게 하면 최소한

  • “정상 동작”은 보장하고
  • “자주 터지는 경계/에러”도 같이 잡습니다

자동 생성 테스트가 “쓸모없는 테스트”가 되지 않게 만드는 장치예요.


모킹 규칙: 과하게 하지 않기 (테스트가 깨지는 진짜 이유)

테스트가 깨지기 쉬운 가장 흔한 원인이 과도한 모킹이더라고요.
내부 구현을 잔뜩 mock 해버리면, 코드가 조금만 바뀌어도 테스트가 와르르 깨집니다.

그래서 /create-jest는 원칙이 단순합니다.

  • 외부 의존성만 모킹한다 (네트워크, 파일 시스템, 외부 API)
  • 내부 구현은 가능한 실제로 실행한다
// ✅ Good: 외부 의존성만 모킹
const mockFetch = jest.fn();
global.fetch = mockFetch;

// ❌ Bad: 내부 구현을 과하게 모킹
jest.mock("./internal-helper");

이렇게 하면 테스트가 “구현 디테일”에 덜 묶여서 유지보수가 쉬워집니다.


기존 테스트가 있으면? “갈아엎지 않고” 업데이트만

자동화의 또 다른 함정은 이거예요.

“테스트 새로 만들어줬는데… 기존 테스트 스타일이랑 완전 다르네?”

그래서 변경된 파일에 테스트가 이미 있으면 /create-jest는 이렇게 행동합니다.

  1. 기존 테스트 구조 유지
  2. 변경된 기능에 대한 테스트만 수정/추가
  3. 삭제된 기능의 테스트 제거
  4. 새 기능의 테스트 추가
  5. 전체 테스트가 통과하는지 확인

즉, 전체를 다시 쓰지 않고 변경분만 안전하게 다룹니다.
(이게 안 되면 팀 프로젝트에서 바로 반발 나옵니다…)


실제 사용 예시: “재시도 로직 추가했더니 테스트도 같이 자랐다”

예를 들어 libs/api/client.ts에 재시도 로직을 추가했다고 해볼게요.

# 1) 코드 수정 후 변경 파일 확인
git diff --name-only
# libs/api/client.ts

# 2) 테스트 생성/업데이트
/create-jest

그러면 결과는 이런 식으로 요약됩니다.

## 테스트 작성 완료

### 업데이트된 테스트
- libs/api/__tests__/client.test.ts

### 테스트 결과
✓ 12 tests passed
✗ 0 tests failed

### 주요 테스트 케이스
- apiFetch: GET/POST, 재시도, 에러 처리 등
- createApiClient: 인스턴스 생성, baseURL 설정 등

중요한 건 “파일 하나 늘어났네요”가 아니라,
변경한 로직(재시도)이 테스트 케이스로 바로 박제된다는 점입니다.


/spec과 같이 쓰면 “명세→구현→테스트”가 두 줄로 끝난다

2편에서 다룬 /spec과 붙이면 흐름이 깔끔해져요.

/spec user-auth
/create-jest
  • /spec: 명세서 기반 구현 + 검증
  • /create-jest: 변경 기반 테스트 생성 + 실행

이렇게 하면 “테스트를 쓰는 사람”이 아니라,
테스트가 기본으로 따라오는 구조가 됩니다.


왜 이 방식이 효과적이었나

1) “테스트 쓸까?”라는 고민이 사라짐

테스트가 선택이 아니라 루틴이 됩니다.
루틴이 되면 지속됩니다.

2) 테스트 스타일이 통일됨

한글 테스트명, AAA, describe 구조…
팀원마다 다르게 쓰던 테스트가 “명령어 규칙”으로 정렬됩니다.

3) 리뷰어가 편해짐

PR에 테스트가 항상 포함되니,
“테스트는요?”라는 질문 자체가 줄어듭니다.

4) 회귀 테스트가 자동으로 쌓임

수정할 때마다 테스트가 업데이트되니까,
어느 순간부터는 “테스트가 나를 지켜주는 순간”이 오더라고요.


실제 명령어 파일은 이렇게 생겼습니다 (핵심만)

/create-jest 명령어는 결국 규칙을 마크다운으로 적어둔 것입니다.

# create-jest.md

변경된 파일에 대한 테스트 코드를 생성하거나 업데이트합니다.

## 실행 절차
1. git diff로 변경된 파일 목록 확인
2. 테스트 대상 파일 필터링
3. 기존 테스트 여부에 따라 생성 또는 업데이트
4. 테스트 실행하여 통과 확인

## 테스트 대상 규칙
### 포함
- libs/**/*.ts
- hooks/**/*.ts
- utils/**/*.ts
- services/**/*.ts

### 제외
- app/**/*
- components/**/*
- **/*.d.ts
- **/index.ts
- **/__tests__/**
- *.config.*

## 테스트 작성 규칙
1. 한글 테스트명
2. describe 구조화
3. AAA 패턴
4. 모킹은 외부 의존성만

이렇게 “팀 룰”을 문서로 남기면, Claude는 그걸 매번 일관되게 따라줍니다.


커스터마이징 포인트 (프로젝트마다 여기만 바꾸면 됨)

1) 테스트 대상 경로

- src/domain/**/*.ts
- src/application/**/*.ts

2) 테스트 파일 위치

  • 소스 파일 옆에 *.test.ts 생성
    또는
  • 루트 tests/에 동일 폴더 구조로 생성

3) 프레임워크 변경

  • Jest 대신 Vitest
  • Testing Library 규칙 추가

주의사항 (자동 생성 테스트를 “과신”하지 않기)

  • 100% 커버리지가 목표가 아닙니다.
    자동 생성은 “기본 안전망”을 깔아주는 역할이에요.
  • 생성된 테스트는 한 번은 눈으로 검토하세요.
    가끔 표면적인 테스트(예: 단순 snapshot 느낌)가 나올 수 있습니다.
  • 테스트가 쌓이면 실행 시간이 늘어요.
    CI에서 병렬 실행/캐싱 같은 운영 최적화도 고려해볼 만합니다.

마치며

/create-jest의 핵심은 한 문장입니다.

테스트 작성을 “의지”에서 “시스템”으로 옮기는 것.

  • “어떻게 테스트하지?” 고민을 없애고
  • 팀 컨벤션에 맞는 테스트를 자동으로 만들고
  • 코드 변경과 테스트가 항상 같이 다니게 만들었습니다

완벽하진 않아요.
하지만 테스트 0개보다
자동 생성된 테스트 10개 + 중요한 곳 수동 보완 2개가 훨씬 낫잖아요.

다음 4편에서는 /w-context를 다룹니다.
AI가 코드를 더 잘 이해하도록 CONTEXT.md를 자동 생성하는 흐름이에요.


이 글에서 소개한 명령어들은 제가 만들고 있는 Link Dropper 프로젝트에서 실제로 사용하고 있습니다.

profile
“기록하는 습관을 도구로 만들다 — 두 개발자의 링크 드라퍼 구축기”

0개의 댓글