https://product.kyobobook.co.kr/detail/S000001248962
테스트 주도 개발 시작하기 | 최범균 - 교보문고
테스트 주도 개발 시작하기 | 작동하는 깔끔한 코드를 만드는 데 필요한 습관 - JUnit 5를 이용한 테스트 주도 개발 안내 - 테스트 작성과 설계를 위한 대역 - 테스트 가능한 설계 방법 안내 - 유지
product.kyobobook.co.kr
1. 테스트 코드 작성 순서 – 기본 원칙
1-1. 암호 강도 측정 예제에서의 실제 순서
- 모든 규칙을 충족하는 암호 → 강함
- 길이만 8 미만이고 나머지는 만족 → 보통
- 숫자를 포함하지 않고 나머지는 만족 → 보통
- 값이 없는 암호(null/빈 문자열) → 유효하지 않음
- 대문자를 포함하지 않고 나머지는 만족하는 경우
- ‘길이 규칙만’ 충족하는 경우
- ‘숫자 규칙만’ 충족하는 경우
- ‘대문자 규칙만’ 충족하는 경우
- 아무 규칙도 충족하지 않는 경우
이 순서는 임의로 만든 게 아니라, 두 가지 기준을 따른 결과
1-2. 두 가지 핵심 규칙
- 쉬운 경우 → 어려운 경우 순서
- 예외적인 경우 → 정상적인 경우 순서
- 너무 꼬인 조합은 나중에, 처음엔 “명확하고 직관적인 케이스”부터,
- 예외적인 상황(널, 빈 값 등)을 비교적 앞쪽에 가져와서 다룬다는 전략
2. 초반에 복잡한 테스트부터 시작하면 안 되는 이유
2-1. 복잡한 테스트의 문제점
예를 들어 암호 강도 측정에서 이렇게 시작한다고 생각해 보자
- “대문자 규칙만 충족하는 경우”
- “모든 규칙을 충족하는 경우”
- “숫자를 제외하고 나머지는 충족하는 경우”
이 순서로 테스트를 만들면:
- 첫 테스트를 통과시키기 위해 규칙 일부만 구현했다가,
- 두 번째 테스트에서 한 번에 큰 범위의 구현을 해버리게 되고,
- 세 번째 테스트가 오면 이미 꼬여 있는 로직에 조건을 덕지덕지 붙이게 된다.
결과:
- 한 번에 구현해야 하는 코드가 많아짐
- 테스트 한 개를 통과시키려 할 때마다 바꿔야 할 코드 양이 커짐
- 그 과정에서 버그가 숨어들기 쉬워짐
- 디버깅 시간이 늘어 TDD의 장점을 못 느끼게 됨
“초반 테스트가 복잡하면, TDD 사이클이 길어지고 지쳐버린다.”
3. 구현하기 쉬운 테스트부터 시작하기
3-1. 쉬운 테스트의 기준
암호 예제에서 쉬운 테스트는 예를 들면:
- “모든 조건을 충족하는 경우”
- “모든 조건을 하나도 충족하지 않는 경우”
왜 쉬울까?
- 로직이 단순한 방향으로만 흘러간다 (조건문 가지치기가 적음)
- 구현도 단순:
- 세 규칙을 모두 만족 → count==3 → STRONG
- 세 규칙 모두 불만족 → count==0 → WEAK
3-2. 점진적 난이도 상승
- 한 규칙만 충족하는 경우
- 두 규칙만 충족하는 경우
핵심
- 한 테스트를 통과시킨 다음, 그다음으로 “조금 더 어려운” 테스트를 고른다.
- 그러면 한 번에 바꿔야 하는 코드 양이 늘지 않고, 디버깅 단위가 작아지면서 TDD가 훨씬 수월해진다.
4. 예외 상황을 먼저 테스트해야 하는 이유
4-1. 예외를 나중에 넣으면 생기는 일
예외 처리(널, 경계값, 이상한 입력)는 보통 이런 걸 유발
- 복잡한 if-else 블록 추가
- 기존 로직 중간중간에 조건문 끼워 넣기
- 코드 구조가 뒤집히거나, 같은 조건을 여러 번 검사하는 중복
예외를 나중에 붙이면코드 구조 자체를 갈아엎어야 할 가능성이 커지고, 이미 짜놓은 테스트/구현이 같이 흔들리게 된다.
4-2. 예외를 앞에서 테스트하면 좋은 점
- 예외 상황을 먼저 테스트해두면, 구현할 때 애초에 예외를 처리하는 구조를 갖고 출발.
- 그래서 나중에 기능 확장을 할 때 구조를 덜 깨고도 로직을 넣을 수 있다.
5. 완급 조절 – 한 번에 얼마나 구현할 것인가
“한 번에 구현하는 양을 얼마나 가져가야 하는가”에 대한 가이드
5-1. 초보 TDD 단계에서의 3스텝
- 정해진 값을 리턴
- 일단 테스트를 통과시키기 위해, 하드코딩된 값으로만 구현.
- 값 비교를 이용해서 정해진 값을 리턴
- 입력 값이 어떤 조건을 만족하면 그 값을 리턴, 아직 일반화는 불완전해도 좋음.
- 테스트를 추가하면서 점점 일반화
- 규칙 케이스(1개 만족, 2개 만족, 0개 만족…)를 늘려가며 조건문/계산 로직을 점진적으로 일반화.
이 3단계는 “너무 완벽하려고 하지 말고, 작게 쪼개서 가자” 라는 훈련
6. 지속적인 리팩토링 – 언제, 어디까지?
리팩토링은 항상 따라붙어야 한다 !
6-1. 리팩토링의 목적
- 소프트웨어는 생명 주기가 길수록 변경을 많이 겪는다.
- 변경이 쉬운 구조를 위해서는 지속적인 구조 개선(리팩토링) 이 필수.
- TDD는 이 리팩토링을 테스트라는 안전망과 함께 하도록 돕는다.
6-2. 어떤 리팩토링을 언제 할까?
- 작은 리팩토링 (상수→변수, 변수 이름, 간단한 중복 제거) → 보이자마자 바로 해도 된다.
- 구조에 영향을 주는 리팩토링 (메서드 추출, 책임 분리 등) → 기능의 큰 흐름이 어느 정도 보이기 시작한 뒤에 한다.
- 구현 초기에는 전체 흐름이 안 보이기 때문에,너무 일찍 구조를 확정해 버리면 틀린 구조를 고집하게 될 수 있음.
- 리팩토링은 항상 GREEN 상태에서만.(테스트 전부 통과한 상태에서 구조를 바꾸고, 다시 테스트 돌려 확인)
7. 테스트 작성 순서 연습 – [정기 유료 서비스 만료일 예제]
7-1. 요구사항 요약
예제에서 주어진 규칙은 세 가지:
- 서비스를 사용하려면 매달 1만 원을 선불로 납부한다.
- 2개월 이상 요금을 한 번에 납부할 수 있다 (2만 원 → 2개월, 3만 원 → 3개월 …).
- 10만 원을 납부하면 1년(12개월) 제공한다.
즉, 입력(납부 금액 / 납부일)으로부터 서비스 만료일(LocalDate 등)을 계산하는 기능을 TDD로 만듬
7-2. 테스트 클래스부터 만들기
- 먼저 “만료일 계산”이라는 맥락이 잘 드러나는 테스트 클래스 이름을 정하고, 테스트 파일만 만들기
7-3. 첫 테스트 – 가장 쉬운 경우
- “1만 원을 납부하면 정확히 한 달 뒤가 만료일이다.”
이 테스트로부터 자연스럽게:
- 만료일은 LocalDate 같은 타입으로 표현하는 게 좋은가?
- 계산할 때 필요한 정보는 최소한 무엇인가? (납부일, 납부액)
- 계산을 담당할 객체/메서드 시그니처는 어떻게 잡을까?
를 하나씩 결정하게 된다.
7-4. 테스트를 늘려가며 일반화
- 1만 원 납부 → 한 달 뒤 만료
- 2만 원 납부 → 두 달 뒤 만료
- 3만 원 납부 → 세 달 뒤 만료
- 10만 원 납부 → 1년 뒤 만료
달이 바뀔 때의 경계 조건도 추가
- 달의 마지막 날에 납부하면 → 다음 달 마지막 날이 만료일
- 예: 1/31 + 1개월 = 2월 마지막 날(28/29일)
테스트를 추가할 때마다 구현을 조금씩 확장/일반화하고, 필요시 리팩토링을 수행해서 “돈 → 개월 수 → 만료일” 로직을 깨끗하게 나눈다.
8. 테스트할 목록 정리하기 – 미리 써두기
8-1. 만료일 예제에서의 목록 예시
- 1만 원 납부하면 한 달 뒤가 만료일
- 달의 마지막 날에 납부하면 다음 달 마지막 날이 만료일
- 2만 원 납부하면 2개월 뒤가 만료일
- 3만 원 납부하면 3개월 뒤가 만료일
- 10만 원 납부하면 1년 뒤가 만료일
이 목록은 두 가지 용도
- 시야 확보 – 앞으로 어떤 케이스를 다뤄야 하는지 감 잡기
- 다음 테스트 선택 – “이 중에서 지금 가장 구현하기 쉬운 건 무엇인가?”를 고를 기준
8-2. 하지만 한 번에 다 쓰지 않는다
- 목록을 적었다고 해서 테스트를 한 번에 다 작성하면 안 됨.
- 테스트가 동시에 여러 개 깨진 상태가 되면,리팩토링/구현 단위가 커져서 리듬이 망가지기 때문.
- 올바른 패턴:
- 테스트 한 개 작성
- 해당 테스트를 통과시키는 최소 구현
- 필요하면 리팩토링
- 다시 새로운 테스트 선택
이 짧은 사이클을 계속 반복
9. 시작이 안 될 때 / 구현이 막힐 때 대처법
멘탈 관리 팁에 가까운 내용
9-1. 시작이 안 될 때는 “단언부터” 작성
테스트를 쓰려는데, 막상 어디서부터 시작해야 할지 막힐 때 검증 코드(assert)부터 쓰자
- 아직 기대만료일/실제만료일 변수가 뭔지도 모르지만, “무엇을 비교해야 하는지”부터 적어두면,
- 만료일을 어떻게 표현할지,
- 실제 만료일을 계산하는 함수/객체는 어떤 모양이어야 할지
- 어떤 파라미터가 필요한지를 역으로 떠올리기 쉬워진다
9-2. 구현이 막히면?
구현 중간에 완전히 꼬였다 싶으면 과감하게 코드를 지우고 다시 시작하는 것도 옵션.
- 다만 “어디서 꼬였는지”를 돌아보기
- 테스트 작성 순서가 너무 복잡하지 않았는지
- 쉬운 케이스/예외 케이스부터 갔는지
- 한 번에 너무 많은 걸 구현하려 한 건 아닌지
이때 되새겨야 할 키워드
- 쉬운 테스트부터
- 예외적인 테스트 먼저
- 완급 조절
핵심정리
- 테스트 작성 순서
- 쉬운 → 어려운
- 예외적인 → 정상적인
- 초반에 복잡한 테스트 금지
- 한 번에 많은 코드 구현 → 버그/디버깅 폭증
- 구현하기 쉬운 테스트부터
- 대표 케이스, 극단 케이스(모두 만족 / 아무도 만족 X)부터
- 예외 상황을 초기에
- 예외를 나중에 붙이면 구조를 뒤집어야 함
- 완급 조절
- 정해진 값 리턴 → 조건 비교 → 점진적 일반화
- 지속적인 리팩토링
- 작은 건 바로, 구조 바꾸는 건 흐름이 잡힌 뒤
- 테스트 작성 순서 연습
- 유료 서비스 만료일 예제로 테스트 목록 만들기 + 순서 조율 연습
- 막힐 때의 팁
- 단언부터 작성
- 순서가 꼬였으면 “쉬운/예외 테스트 + 완급 조절”을 떠올리며 다시 설계
'외부활동 > 우아한테크코스 [프리코스]' 카테고리의 다른 글
| 테스트주도 개발 시작하기 - 챕터 5. JUnit (0) | 2025.11.05 |
|---|---|
| 테스트주도 개발 시작하기 - 챕터 4. TDD, 기능명세, 설계 (0) | 2025.11.05 |
| 테스트주도 개발 시작하기 - 챕터 2. TDD 시작 (0) | 2025.11.04 |
| [우아한테크코스 8기] 프리코스 3주차 회고 (0) | 2025.11.04 |
| 도메인의 성장과 Service 계층의 관계 (0) | 2025.11.02 |