새 할인 기능을 맡았을 때의 일입니다.
AI에게 “할인 가격을 계산하는 함수를 만들어 줘”라고 요청했습니다. 코드는 짧고 문법 오류도 없었습니다.
def final_price(price, discount):
return price * (1 - discount)
그런데 final_price(10000, 10)의 결과는 9000이 아니라 -90000이었습니다. AI는 10을 10%가 아니라 10배로 해석했습니다.
코드는 실행됐지만, 요구사항은 실행되지 않은 셈입니다.

이런 문제는 AI가 계산을 못해서 생기지 않습니다. 사람이 요구사항을 “관찰 가능한 예시”로 바꾸지 않았기 때문에 생깁니다.
테스트는 완성된 코드에서 버그를 찾는 마지막 절차만이 아닙니다. 코드를 만들기 전에 우리가 원하는 동작을 모호하지 않게 설명하는 언어입니다.
“잘 작동한다”를 누가 확인할 수 있는 문장으로 바꾸기
“할인 기능이 잘 작동해야 한다”는 요구사항은 사람마다 다르게 해석할 수 있습니다. 다음 세 문장은 훨씬 선명합니다.
- 정가 10,000원에 할인율 10을 입력하면 9,000원을 반환한다.
- 할인율 0은 원래 가격을 그대로 반환한다.
- 할인율이 100보다 크면 조용히 계산하지 않고 오류를 낸다.
입력과 기대 결과가 생기자 구현 전에 중요한 질문이 드러납니다.
- 할인율의 단위는 퍼센트인가, 소수 비율인가?
- 가격이 음수일 수 있는가?
- 전액 할인은 허용하는가?
- 소수점은 버리나, 반올림하나?
- 잘못된 입력은 어떤 방식으로 실패해야 하는가?
좋은 테스트는 이미 정해진 답을 검사하는 것이 아니라, 아직 정하지 않은 제품 결정을 발견하게 합니다.
요구사항을 테스트로 바꾸는 5칸
기능 하나를 받으면 다음 다섯 칸부터 채워 보세요.
- 정상 사례: 사용자가 가장 흔하게 하는 행동
- 경계값: 의미가 바뀌는 시작점과 끝점
- 잘못된 입력: 시스템이 거절해야 하는 값
- 권한 없음: 할 수 없는 사용자가 시도하는 행동
- 외부 실패: 결제·메일·DB 같은 의존성이 실패하는 상황

할인 계산 예시는 다음처럼 정리할 수 있습니다.
구분입력기대 결과막는 회귀
| 정상 | 10000, 10 | 9000 | 퍼센트와 소수 비율 혼동 |
| 경계값 | 10000, 0 | 10000 | 할인 없음 처리 오류 |
| 경계값 | 10000, 100 | 0 | 전액 할인 처리 오류 |
| 잘못된 입력 | -1, 10 | ValueError | 음수 가격 허용 |
| 잘못된 입력 | 10000, 101 | ValueError | 할인율 범위 초과 |
로그인이나 결제처럼 사용자와 외부 시스템이 얽힌 기능이라면 권한과 외부 실패 칸도 반드시 추가합니다. 정상 사례 하나만 통과하는 테스트는 “행복한 경로”만 증명할 뿐, 제품이 실제 환경을 견딘다는 증거는 아닙니다.
구현 공식이 아니라 사용자에게 보이는 동작을 검사하기
테스트를 촘촘하게 작성해도 구현 내부에 지나치게 묶이면 작은 리팩터링마다 깨집니다.
나쁜 테스트는 함수 내부에서 특정 보조 함수가 정확히 두 번 호출됐는지, 중간 변수가 어떤 값을 가졌는지만 확인합니다. 이런 정보가 제품 계약이 아니라면 사용자가 보는 결과가 같아도 테스트가 실패합니다.
좋은 테스트는 다음을 확인합니다.
- 사용자가 어떤 값을 입력했는가?
- 화면이나 응답에서 무엇을 보게 되는가?
- 실패했을 때 어떤 오류를 받는가?
- 데이터나 결제가 의도한 대로 보존되는가?
예를 들어 할인 함수의 핵심 계약은 계산 공식을 그대로 복사해 비교하는 것이 아닙니다.
import unittest
class FinalPriceTest(unittest.TestCase):
def test_ten_percent_discount_returns_9000(self):
self.assertEqual(final_price(10000, 10), 9000)
def test_zero_percent_keeps_original_price(self):
self.assertEqual(final_price(10000, 0), 10000)
def test_discount_over_100_is_rejected(self):
with self.assertRaises(ValueError):
final_price(10000, 101)
테스트 이름만 읽어도 제품의 동작을 설명할 수 있어야 합니다. 구현이 바뀌어도 이 문장이 여전히 참이라면 테스트도 그대로 남을 수 있습니다.
테스트 한 개에는 세 문장이 필요하다
테스트를 작성하기 전에 아래 세 문장을 자연어로 먼저 적어 봅니다.
- 상황: 어떤 조건과 사용자인가?
- 행동: 무엇을 실행하는가?
- 결과: 사용자가 무엇을 관찰해야 하는가?
예를 들면 이렇습니다.
정가가 10,000원이고 할인율이 10%일 때, 사용자가 최종 가격을 계산하면, 9,000원을 받는다.
이 구조는 흔히 Given–When–Then이라고 부르지만 이름을 외우는 것보다 세 문장이 서로 이어지는지가 중요합니다. 결과에 “정상 처리된다” 대신 구체적인 값, 상태, 오류를 적어야 자동 테스트로 번역할 수 있습니다.

테스트가 알려 주는 것은 코드 품질보다 요구사항의 빈칸이다
테스트 사례를 만들다가 답하기 어려운 질문이 나오면, AI에게 임의로 결정하게 두지 않습니다.
예를 들어 10,000원에 12.345%를 적용한 결과가 8765.5인지 8766인지 확실하지 않다면 구현 문제가 아니라 정책 문제입니다. 제품 담당자에게 확인해 다음처럼 결정해야 합니다.
최종 금액의 소수점 이하는 원 단위에서 반올림한다.
그다음 이 문장을 테스트로 남깁니다. 테스트는 회의에서 합의한 결정을 코드 저장소 안에 보존하는 문서가 됩니다.
AI에게 구현보다 테스트 표를 먼저 요청하기
아래 프롬프트를 기능에 맞게 바꿔 사용해 보세요.
[함수 또는 기능]의 테스트를 먼저 설계해 줘.
정상, 경계값, 잘못된 입력, 권한 없음, 외부 의존성 실패로 나눠 표로 정리해 줘.
각 사례에는 상황과 입력, 사용자가 관찰할 기대 결과, 이 테스트가 막는 회귀를 적어 줘.
구현 세부사항이 아니라 사용자에게 보이는 동작을 검사해 줘.
요구사항이 모호해 임의로 결정해야 하는 부분은 구현하지 말고 확인 질문으로 분리해 줘.
AI가 만든 테스트도 그대로 정답은 아닙니다. 존재하지 않는 정책을 그럴듯하게 채우거나 중요한 실패 경로를 놓칠 수 있습니다. 다음 순서로 사람이 검토합니다.
- 예시 하나만 반복한 테스트는 없는가?
- 0, 최댓값, 빈 입력처럼 의미가 바뀌는 경계가 있는가?
- 잘못된 값이 조용히 저장되거나 계산되지 않는가?
- 권한과 외부 실패가 실제 사용자 경험으로 표현됐는가?
- 테스트가 구현 공식이 아니라 제품 계약을 검사하는가?
오늘 바로 해 보는 20분 연습
현재 만들고 있는 기능 하나를 고릅니다. 코드가 이미 있어도 괜찮습니다.
- “잘 작동한다”라는 표현을 지우고 대표 입력과 기대 결과를 한 줄로 씁니다.
- 정상·경계값·잘못된 입력·권한·외부 실패 다섯 칸을 채웁니다.
- 각 사례가 막는 회귀를 한 문장으로 적습니다.
- 답을 정하기 어려운 항목은 제품 질문으로 분리합니다.
- 테스트 이름만 읽고 동료가 요구사항을 설명할 수 있는지 확인합니다.
다섯 칸을 모두 채울 수 없다고 해서 나쁜 기능은 아닙니다. 계산 함수에는 권한이나 외부 실패가 없을 수 있습니다. 중요한 것은 해당 없음이라고 판단한 이유까지 알고 있는 것입니다.
다음 글에서는 AI가 만든 PR을 리뷰할 때 테스트 결과만 보는 대신, 변경 목적·고위험 파일·범위 밖 변경·라이선스까지 확인하는 순서를 다루겠습니다.
이번 글의 한 문장
테스트는 완성된 코드의 점수를 매기는 절차가 아니라, 모호한 요구사항을 입력·기대 결과·실패 방식으로 번역해 팀이 함께 읽는 계약이다.
'Coding' 카테고리의 다른 글
| AI 대화 50줄을 그대로 보고하지 마세요: 팀을 움직이는 5문장 보고법 (0) | 2026.09.18 |
|---|---|
| AI가 만든 PR, 코드부터 읽지 마세요: 큰 변경을 검토하는 6단계 (0) | 2026.09.15 |
| 버그를 만나면 바로 고치지 마세요: 원인을 좁히는 디버깅 가설 4단계 (0) | 2026.09.14 |
| 처음 보는 저장소, 어디서부터 읽을까? 30분 만에 코드 지도 만드는 5단계 (0) | 2026.09.11 |
| 큰 기능을 1~2시간 작업으로 나누는 법: AI 코딩이 덜 헤매는 작업 분해 5단계 (0) | 2026.09.10 |
댓글