AI가 코드를 짜는 시대에, 테스트 전략은 그대로여도 될까요?
사람이 코딩할 때는 테스트를 "작성하는 비용"이 핵심이었습니다. 시간이 오래 걸리니까, 가성비 좋은 계층에 집중하라는 게 정석이었어요. 그런데 AI가 코드를 짜기 시작하면서, 이 비용 구조가 근본적으로 바뀌었습니다.
이 글에서는 테스트 트로피 개념부터 시작해서, AI 시대에 테스트 전략을 어떻게 재설계해야 하는지 정리해볼게요.
2018년, Kent C. Dodds가 "테스트 트로피(Testing Trophy)"라는 개념을 제시했습니다. Guillermo Rauch(Socket.io, Next.js 창시자)의 한 줄에서 출발했어요.
"Write tests. Not too many. Mostly integration."
(테스트를 작성하라. 너무 많이는 말고. 대부분은 integration으로.)
테스트 트로피는 4개의 계층으로 이루어져 있습니다.
아래에서부터 Static, Unit, Integration, E2E. 트로피 형태이기 때문에 가운데(Integration)가 가장 넓어요. 여기에 가장 많은 테스트를 집중하라는 뜻입니다.
그 이전에는 "테스트 피라미드(Testing Pyramid)"가 정석이었어요. 2009년 Mike Cohn이 제안하고 Martin Fowler가 정리한 구조인데, Unit 테스트를 기반(가장 넓은 계층)으로 쌓는 피라미드 형태입니다.
| 테스트 피라미드 (2009) | 테스트 트로피 (2018) | |
|---|---|---|
| 가장 넓은 계층 | Unit | Integration |
| Static 포함 여부 | 없음 | 있음 (기반 계층) |
| 핵심 철학 | 빠르고 격리된 테스트를 많이 | 사용 방식과 닮은 테스트를 |
Kent C. Dodds의 핵심 원칙은 이겁니다.
"The more your tests resemble the way your software is used, the more confidence they can give you."
(테스트가 소프트웨어의 실제 사용 방식과 닮을수록, 더 높은 신뢰를 준다.)
Unit 테스트는 함수 하나를 격리해서 검증합니다. 하지만 사용자는 함수 하나를 호출하지 않아요. 버튼을 누르고, 폼을 제출하고, 결과를 확인합니다. Integration 테스트가 이 사용 패턴에 더 가까운 거예요.
테스트 트로피가 Integration을 강조하는 이유는 비용 대비 신뢰도 때문입니다.
위로 올라갈수록(E2E → Integration → Unit → Static) 실행 비용이 올라갑니다.
테스트 작성 + 실행 비용
그래서 결론이 나옵니다. 가성비 좋은 Integration에 집중하라.
특히 프론트엔드에서 Unit 테스트는 현실적으로 힘든 면이 있어요. UI가 워낙 자주 바뀌기 때문입니다. 버튼 텍스트 하나 바꿨는데 Unit 테스트가 5개 깨지는 경험, 해보신 분은 아실 겁니다. 테스트를 유지하는 비용이 테스트의 가치를 넘어서는 순간이 오는 거예요.
이 모든 논의의 전제는 하나입니다. 사람이 직접 테스트를 작성한다는 것.
AI 코딩 도구(Claude Code, Cursor, GitHub Copilot)가 등장하면서, 테스트를 "작성하는 비용"이 극적으로 떨어졌습니다.
실제 데이터를 보면 분명합니다.
사람이 작성
1x
기준 속도
$$
엔지니어 시간 = 비용
Not too many
비용 때문에 제약
AI가 작성
9x
생성 속도
<$1
E2E 44개 생성 비용
제약 없음
비용 → 0 수렴
사람이 Unit 테스트 하나 작성하는 시간에, AI는 전체 테스트 스위트를 생성합니다. 비용이 0에 수렴하면, "Not too many"라는 제약 자체가 의미를 잃어요.
그러면 전략이 바뀌어야 합니다.
AI가 코딩할 때, Unit과 Integration 테스트는 별도 공수가 아닙니다. 코드를 생성하면서 테스트 코드도 함께 만들어내는 거예요. "테스트를 작성해야지"라고 따로 결심할 필요가 없습니다. AI에게 "테스트도 같이 짜줘"라고 하면 끝이에요.
여기서 흥미로운 전환이 일어납니다. AI가 코드를 짜는 상황에서는, 차라리 mock 기반 E2E 테스트가 더 효율적이에요.
왜 mock이냐고요? 진짜 E2E는 외부 서비스 의존성 때문에 불안정합니다. Mock으로 외부를 격리하면 안정성을 확보하면서도 넓은 범위를 커버할 수 있어요.
그리고 결정적으로, 나중에 진짜 E2E로 교체할 수 있습니다. Mock만 걷어내면 되니까요. Mock E2E는 진짜 E2E로 가는 디딤돌이에요.
사람이 코딩할 때는 E2E 작성 비용이 너무 높아서 Integration에서 타협했습니다. AI가 코딩하면 그 비용 장벽이 사라집니다. 타협할 이유가 없어진 거예요.
Mock E2E가 아무리 좋아도, 절대 대체할 수 없는 계층이 하나 있습니다. 트로피의 맨 아래, Static 테스트입니다.
Static 테스트란 코드를 실행하지 않고 잡는 오류예요. 구체적으로는 세 가지입니다.
왜 E2E로 대체가 안 될까요? 근본적으로 검사 범위가 다르기 때문입니다.
E2E 테스트는 실행 경로만 검증합니다. 테스트 시나리오가 거치는 코드만 확인해요. 하지만 Static 분석은 프로젝트의 모든 코드를, 실행 없이, 한 번에 검사합니다.
예를 들어볼게요.
async function fetchUser(id: string) {
const response = await fetch(`/api/users/${id}`);
response.json(); // Promise를 await 하지 않음
}
이 코드는 E2E 테스트를 통과할 수 있습니다. 응답이 빠르면 타이밍상 문제가 드러나지 않을 수 있으니까요. 하지만 no-floating-promises ESLint 규칙은 이걸 즉시 잡아냅니다. await 없이 떠다니는 Promise는 에러 처리가 불가능하다는 걸 구조적으로 감지하는 거예요.
TypeScript와 ESLint는 서로 상호보완 관계입니다.
| TypeScript | ESLint | |
|---|---|---|
| 잡는 것 | 타입 불일치, 존재하지 않는 속성 | 논리적 안티패턴, 코드 품질 |
| 예시 | string을 number에 할당 | 미처리 Promise, unsafe any 사용 |
| 범위 | 타입 시스템 내 오류 | type-safe하지만 논리적으로 깨진 코드 |
ESLint가 잡는 no-floating-promises, no-unsafe-member-access, unbound-method 같은 규칙들은 타입 체크를 통과하지만 런타임에서 터지는 코드를 잡아냅니다.
핵심은 이겁니다. AI 시대에 static 테스트의 ROI가 오히려 올라갑니다.
AI가 코드를 빠르게 대량 생성할수록, 그 코드를 구조적으로 검증할 안전망이 더 중요해져요. E2E는 "동작하는지"를 보고, Static은 "올바르게 작성되었는지"를 봅니다. 둘은 대체 관계가 아니라 보완 관계입니다.
지금까지의 흐름을 정리하면 이렇습니다.
양 끝(Static, E2E)은 넓어지고, 가운데(Unit, Integration)는 좁아집니다.
이 모양, 어딘가 익숙하지 않나요?
모래시계입니다.
| 시대 | 모양 | 가장 넓은 계층 | 전제 |
|---|---|---|---|
| 2009 수동 코딩 | 피라미드 ▲ | Unit | 사람이 작성, 빠른 테스트에 집중 |
| 2018 모던 FE | 트로피 🏆 | Integration | 사람이 작성, 가성비에 집중 |
| 2025 AI 코딩 | 모래시계 ⏳ | Static + E2E | AI가 작성, 양 끝단 최대화 |
Unit과 Integration이 사라지는 게 아닙니다. AI가 코딩 중에 알아서 채우는 영역이 되는 거예요. 사람이 전략적으로 신경 써야 할 곳은 양 끝단입니다.
아래쪽(Static): 한 번 설정하면 자동으로 돌아가는 안전망. 유지보수 비용이 거의 0.
위쪽(E2E): AI가 저비용으로 대량 생성. Mock으로 시작해서 진짜 E2E로 교체 가능.
가운데(Unit/Integration): AI의 부산물. 별도 전략 불필요.
Static 테스트가 중요하다는 건 알겠는데, 실제로 어떻게 설정할까요?
여기서 흔한 실수가 있습니다. AI에게 이렇게 시키는 거예요.
"ESLint 룰 추천해줘."
이러면 AI는 일반적인, 무난한 룰셋을 추천합니다. 프로젝트 맥락이 없으니까요. React Native 프로젝트에 Next.js 용 플러그인을 추천하거나, 너무 엄격해서 생산성을 죽이는 룰셋이 나올 수 있어요.
대신 이렇게 시켜야 합니다.
단순 질문
"ESLint 룰 추천해줘."
기준 보강 질문
"이 React Native 프로젝트에 적합한 ESLint 플러그인을 추천해줘."
핵심은 판단 기준을 명시적으로 보강하는 것입니다.
AI는 주어진 기준에 맞춰 판단합니다. 기준이 없으면 AI 나름의 기본값으로 추천하고, 기준이 구체적이면 그에 맞는 정밀한 추천을 합니다. 같은 AI에게 같은 질문을 해도, 기준을 얼마나 잘 설계했느냐에 따라 결과 품질이 달라지는 거예요.
여기서 한 단계 더 나갈 수 있습니다. Multi-agent 조사입니다.
하나의 AI에게 물어보면 하나의 관점에서 답합니다. 하지만 여러 에이전트를 동원해서 다각도로 조사하고, 결과를 종합하면 편향이 줄어들어요. "A 에이전트는 npm trends를 조사하고, B 에이전트는 GitHub 이슈를 분석하고, C 에이전트는 커뮤니티 평가를 수집해서 종합 보고해줘" — 이런 식으로요.
단순한 질문 하나의 차이가, 프로젝트 전체의 코드 품질 기반을 바꿀 수 있습니다.
Static 테스트 설정을 마쳤다면, 다음 질문은 "언제 실행하느냐"입니다.
사람이 IDE에서 코딩하면, 에디터가 실시간으로 빨간 줄을 그어줍니다. ESLint와 TypeScript가 뒷단에서 파일을 감시하면서, 문제가 생기는 즉시 알려주는 거예요.
이게 가능한 이유는 사람이 IDE를 쓰기 때문입니다.
AI 코딩 에이전트(Claude Code 등)는 IDE를 거치지 않습니다. 터미널에서 직접 파일을 수정해요. 에디터의 실시간 린팅이 작동하지 않습니다.
그러면 두 가지 선택지가 생깁니다.
1. 실시간으로 잡기: 파일 수정할 때마다 lint를 돌린다.
2. 나중에 한꺼번에 잡기: 코딩이 다 끝난 후 일괄 실행한다.
각각 이득인 상황이 다릅니다.
실시간이 이득일 때: 변경 범위를 미리 모를 때. props 타입을 바꾸면 그걸 import하는 모든 파일에 에러가 전파돼요. 나중에 lint 돌려서 하나하나 찾아 수정하려면 번거롭습니다.
한꺼번에 잡는 게 이득일 때: 변경 범위를 미리 알 때. 리팩토링처럼 어떤 파일들이 바뀌는지 사람이 이미 알고 있다면, 다 끝나고 한 번에 돌리는 게 효율적이에요.
그런데 AI 코딩에서는 한꺼번에 잡는 전략이 더 실용적입니다. 이유는 간단해요. AI가 에러 피드백을 받으면, 알아서 잘 고치기 때문입니다. Cross-file 에러가 전파되어도, AI에게 "lint 에러 다 고쳐줘"라고 하면 됩니다.
Claude Code에는 Hook이라는 기능이 있습니다. 특정 시점에 자동으로 명령을 실행하는 거예요.
Stop Hook은 Claude가 작업을 마치고 "끝났습니다"라고 말하려는 순간에 실행됩니다. 여기에 lint와 type check를 걸어두면, 코딩 수정이 끝나고 한꺼번에 잡을 수 있어요.
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "npx tsc --noEmit && npx eslint . --ext .ts,.tsx",
"timeout": 60
}
]
}
]
}
}
이 설정이 하는 일은 단순합니다.
사람의 IDE에서 빨간 줄이 하던 역할을, Stop Hook이 대신하는 겁니다.
PostToolUse Hook을 쓰면 파일 수정 직후마다 체크하는 것도 가능해요. 하지만 대부분의 경우 Stop Hook으로 충분합니다. AI가 여러 파일을 수정하는 중간에 매번 lint를 돌리면 오히려 비효율적이에요. 다 끝나고 한 번에 돌려서, 에러가 있으면 AI가 한꺼번에 고치는 게 깔끔합니다.
정리하면 이렇습니다.
사람 시대의 테스트 트로피: 테스트 작성 비용이 높으니, 가성비 좋은 Integration에 집중하라.
AI 시대의 테스트 모래시계: 테스트 작성 비용이 0에 수렴하니, 양 끝단을 최대화하라.
테스트 "작성 비용"이 아니라 "유지보수 비용"이 새로운 병목입니다. 그리고 Static 테스트는 유지보수 비용이 거의 0이에요. 한 번 설정하면 끝이니까요. 룰을 바꾸지 않는 한, 매번 알아서 돌아갑니다.
피라미드는 수동 테스트 시대의 답이었습니다.
트로피는 모던 프론트엔드 시대의 답이었습니다.
모래시계는 AI 코딩 시대의 답입니다.
도구가 바뀌면 전략도 바뀌어야 합니다. 테스트 전략을 모래시계로 다시 그려보세요.
이런 변화를 함께 탐구하고 싶다면, 회원 가입해 주세요. 앞으로도 직접 써보고 발견한 것들을 이곳에서 나누려 해요. 😊