테스트 주도 개발
Test-driven Development
방법
만들 동작을 확인하는 테스트를 먼저 쓰고 그 테스트를 통과시키는 방향으로 구현을 채워 가는 방식입니다.
등장 배경
테스트는 나중에 쓰기로 하면 대개 쓰이지 않습니다. 기능이 동작하기 시작한 뒤에는 다음 일이 기다리고 있고, 이미 되는 것을 다시 확인하는 작업은 늘 뒤로 밀립니다.
밀리지 않고 쓰인 경우에도 다른 문제가 남습니다. 페이지덱의 요금 계산 코드가 그랬습니다. 태오가 구현을 끝낸 뒤 테스트를 붙였고 전부 통과했습니다. 두 달 뒤 무료 플랜 계정에 요금이 청구되는 사고가 났습니다. 계산 자체가 틀렸는데 테스트도 같이 틀려 있었습니다. 코드를 보면서 테스트를 썼으니 코드가 하는 일을 그대로 적어 둔 셈이고, 잘못된 동작까지 함께 고정해 둔 것입니다.
순서를 뒤집으면 이 두 문제가 함께 풀립니다. 테스트를 먼저 쓰면 미룰 수가 없고, 구현을 보고 쓸 수 없으니 무엇이 맞는 동작인지를 먼저 정하게 됩니다.
사용 절차
실패하는 테스트를 만들고, 통과시키고, 정리하는 세 단계를 짧게 반복합니다. 한 주기는 보통 5분에서 15분입니다.
- 다음에 만들 동작을 한 문장으로 적습니다. 무료 플랜 계정의 이번 달 청구액은 0원이다처럼 입력과 결과가 드러나게 적습니다. 한 주기에 하나만 다룹니다.
- 그 문장을 확인하는 테스트를 씁니다. 이때 테스트는 반드시 실패해야 합니다. 구현이 없는데 통과한다면 테스트가 엉뚱한 것을 확인하고 있다는 뜻이라, 이 확인이 이 방식에서 가장 자주 건너뛰는 단계입니다.
- 통과할 만큼만 구현합니다. 앞으로 필요할 것 같은 처리를 미리 넣지 않습니다. 필요해지면 그때 새 테스트가 그것을 요구합니다. 무료 플랜만 다루는 테스트 하나를 통과시키려면 0원을 돌려주는 한 줄로 충분하고, 그 한 줄이 우스워 보여도 다음 주기에서 월 결제 테스트가 들어오면 계산이 자연히 자랍니다.
- 통과한 뒤에 정리합니다. 동작은 그대로 두고 구조만 고치는 리팩터링을 이 자리에서 합니다. 방금 쓴 테스트가 안전망이라 고치다 깨뜨리면 즉시 드러납니다.
- 1번으로 돌아갑니다. 주기가 한 시간을 넘기면 이 방식의 이점이 사라집니다. 실패 지점이 넓어져 어디서 어긋났는지 다시 찾아야 하기 때문입니다.
- 떠오른 경우는 목록에 적어 두고 지금 다루지 않습니다. 계산 코드를 쓰다 보면 해지한 달의 처리나 연간 결제 할인이 떠오르는데, 그것을 지금 구현하면 주기가 깨집니다. 옆에 적어 두고 다음 주기에서 하나씩 꺼냅니다. 이 목록이 길어지는 것은 문제가 아니라 그 기능의 경우가 그만큼 많다는 사실이 드러난 것입니다.
테스트는 대개 함수나 모듈 하나를 떼어 확인하는 단위 테스트로 씁니다. 빨라야 짧은 주기를 돌릴 수 있고, 몇 초 안에 결과가 나오지 않으면 사람은 주기를 건너뛰기 시작합니다.
적합한 상황과 한계
규칙이 분명하고 입력과 결과가 뚜렷한 코드에 잘 맞습니다. 요금 계산, 권한 판정, 형식 변환이 대표적입니다. 이런 코드는 경우의 수가 많은데 화면으로 확인하려면 매번 조건을 만들어야 해서, 테스트를 먼저 쓰는 편이 오히려 빠릅니다.
버그를 고칠 때도 잘 맞습니다. 재현하는 테스트를 먼저 써서 실패시키고 그다음 고치면, 고쳤다는 사실과 다시 나지 않는다는 사실이 한 번에 남습니다.
한계는 넷입니다.
- 화면과 시각 요소에는 맞지 않습니다. 버튼이 알맞은 자리에 있는지, 색이 어울리는지는 테스트로 적을 수 없습니다. 이쪽은 사람이 보는 확인이 남습니다.
- 무엇을 만들지 모르는 단계에서는 비용만 늘어납니다. 방향을 찾느라 하루에 세 번 구조를 바꾸는 중이라면 테스트도 세 번 다시 써야 합니다. 버릴 코드로 방향을 확인한 뒤 남기기로 정한 부분부터 이 방식으로 다시 쓰는 편이 낫습니다. 이때 버릴 코드라는 것을 팀에 분명히 말해 두지 않으면 그 코드가 그대로 운영에 올라갑니다.
- 외부에 의존하는 코드는 준비가 큽니다. 결제 대행사나 메일 발송처럼 밖으로 나가는 부분은 대역을 만들어 세워야 하고, 그 대역이 실제와 달라지면 테스트는 통과하는데 운영에서 깨집니다.
- 테스트를 구현 세부에 붙이면 정리할 때마다 깨집니다. 내부 함수 이름과 호출 횟수를 확인하는 테스트는 구조를 바꾸는 순간 빨간불이 됩니다. 밖에서 본 동작을 확인하도록 써야 4번 단계가 성립합니다.
실무 용례
"테스트부터 쓸게요. 지금 통과하면 테스트가 잘못된 거예요."
실패를 확인하는 단계를 건너뛰지 않겠다는 말입니다.
"이 버그는 재현 테스트 먼저 만들고 고치죠."
고침과 재발 방지를 한 번에 남기자는 제안입니다.
"화면 쪽은 이 방식으로 안 하고 계산부만 하겠습니다."
부분 적용이 전제라는 뜻입니다.
요금 사고 뒤에 태오가 계산 코드를 이 방식으로 다시 썼습니다. 먼저 경우를 목록으로 적었는데 열네 가지가 나왔습니다. 무료 플랜, 월 결제, 연 결제, 기간 중간에 올린 경우, 기간 중간에 내린 경우, 해지한 달, 환불이 걸린 달 같은 것들입니다. 열네 개를 한 주기에 하나씩 테스트로 옮기는 데 이틀이 걸렸고, 그 과정에서 기존 구현이 틀렸던 경우가 셋 더 나왔습니다. 처음 사고가 난 무료 플랜 건은 그중 하나였습니다.
흔한 오해
테스트 커버리지를 올리는 활동이라고 읽는 경우가 가장 많습니다. 결과적으로 테스트가 늘어나니 그렇게 보입니다. 하지만 목적은 숫자가 아니라 설계와 피드백입니다. 테스트를 먼저 쓰면 그 코드를 밖에서 어떻게 부를지 먼저 정하게 되고, 부르기 어렵다는 느낌이 들면 그것이 구조를 고칠 신호가 됩니다. 테스트 커버리지는 이 과정에서 따라오는 값이고, 그 숫자를 목표로 삼으면 통과만 하는 테스트를 채워 넣는 쪽으로 움직입니다.
모든 코드를 이 방식으로 써야 한다고 보는 것도 흔합니다. 방식의 이름이 개발 전체를 가리키니 전부를 뜻하는 것처럼 읽힙니다. 실제로 이 방식을 쓰는 팀도 화면 조립이나 설정 코드에는 쓰지 않습니다. 규칙이 몰려 있고 틀리면 돈이나 권한이 걸리는 부분에 쓰고, 나머지는 만든 뒤 필요한 곳에 테스트를 붙입니다. 페이지덱에서 이 방식으로 쓰인 코드는 전체의 2할쯤이고, 그 2할이 요금과 권한과 사용량 집계입니다. 전부 아니면 안 한다는 태도가 이 방식을 시작하지 못하게 만드는 가장 흔한 이유입니다.
같이 보면 좋은 페이지
마지막 업데이트 2026-09-16