TDD란?
기능을 검증하는 테스트 코드를 먼저 작성하고 테스트를 통과시키기 위해 개발을 진행하는 개발 방법
TDD 예제 코드
요구 기능
- 암호검사기
요구 사항
- 문자열을 검사해서 아래 규칙 모두를 충족하면 '강함', 두 가지만 충족하면 '보통', 한가지만 충족하면 '약함'으로 구분
- 암호 검사 규칙
- 길이가 8글자 이상
- 0부터 9 사이의 숫자를 포함
- 대문자 포함
참고: 테스트 케이스가 많아 중간중간 생략하고 TDD의 흐름을 볼 수 있는 코드 위주로 작성했다.
첫 번째 테스트: 모든 규칙을 충족하는 경우
- 첫번째 테스트를 선택할 때에는 가장 쉽거나 가장 예외적인 상황을 선택해야 후에 진행과정이 수월하다.
- 암호 검사기에서 가장 쉽거나 예외적인 상황
- 모든 규칙을 충족하는 경우 (조건 검사 코드 없이 ‘강함’에 해당하는 값을 리턴하면 테스트 통과 가능)
- 모든 조건을 충족하지 않는 경우 (각 조건을 검사하는 코드를 모두 구현해야 함, 구현양이 많음)
1. 첫 번째 테스트 메서드 작성 (Red)
public class PasswordStrengthMeterTest {
@Test
void meetsAllCriteria_Then_Strong() {
PasswordStrengthMeter meter = new PasswordStrengthMeter();
PasswordStrength result = meter.meter("ab12!@AB");
assertEquals(PasswordStrength.STRONG, result);
}
}
- PasswordStrengthMeter와 PasswordStrength가 존재하지 않기 때문에 컴파일 에러 발생
2. 컴파일 에러가 발생하지 않도록 구현 클래스 추가 (Red -> Green)
// src/test/java/chap02/PasswordStrength.java
public enum PasswordStrength {
STRONG
}
// ----
// src/test/java/chap02/PasswordStrengthMeter.java
public class PasswordStrengthMeter {
public PasswordStrength meter(String s) {
return null;
}
}
- 테스트 메서드를 통과시킬 만큼의 코드만(컴파일 에러를 없애기 위한 최소 단위의 코드) 작성
- 패키지를 잘 보면 test패키지 하위에 클래스 추가
- 아직 미구현된 클래스이기 때문에 배포에 포함되지 않도록 test패키지 하위에 추가
3. 테스트를 통과시키기 위한 구현 코드 수정 (Red -> Green)
// src/test/java/chap02/PasswordStrengthMeter.java
public class PasswordStrengthMeter {
public PasswordStrength meter(String s) {
return PasswordStrength.STRONG;
}
}
- 컴파일 에러를 없애도 테스트에 통과하지 않기 때문에 테스트를 통과시킬 만큼의 코드를 작성
세 번째 테스트: 숫자를 포함하지 않고 나머지 조건은 충족하는 경우
1. 테스트 메서드 작성 (Red)
@Test
void meetsOtherCriteria_except_for_number_Then_Normal() {
PasswordStrengthMeter meter = new PasswordStrengthMeter();
PasswordStrength result = meter.meter("ab!@ABqwer");
assertEquals(PasswordStrength.NORMAL, result);
}
2. 테스트를 통과시키기 위한 구현 코드 수정 (Red -> Green)
public class PasswordStrengthMeter {
public PasswordStrength meter(String s) {
if(s.length() < 8) {
return PasswordStrength.NORMAL;
}
boolean containsNum = false;
for(char ch : s.toCharArray()) {
if(ch >= '0' && ch <= '9') {
containsNum = true;
break;
}
}
if(!containsNum) {
return PasswordStrength.NORMAL;
}
return PasswordStrength.STRONG;
}
}
3. 구현 코드 리팩토링 (Refactor)
public class PasswordStrengthMeter {
public PasswordStrength meter(String s) {
if(s.length() < 8) {
return PasswordStrength.NORMAL;
}
boolean containsNum = this.meetsContainingNumberCriteria(s);
if(!containsNum) {
return PasswordStrength.NORMAL;
}
return PasswordStrength.STRONG;
}
private boolean meetsContainingNumberCriteria(String s) {
for(char ch : s.toCharArray()) {
if(ch >= '0' && ch <= '9') {
return true;
}
}
return false;
}
}
- 숫자를 포함하는지 여부를 검사하는 코드를 메서드로 분리
4. 테스트 코드 정리 (Refactor)
@Test
void 메서드 이름() {
PasswordStrengthMeter meter = new PasswordStrengthMeter();
PasswordStrength result = meter.meter(암호);
assertEquals(PasswordStrength.값, result);
}
- 테스트 코드를 보면 위와 같은 형태가 반복된다.
- 리팩토링 후
public class PasswordStrengthMeterTest {
PasswordStrengthMeter meter = new PasswordStrengthMeter();
private void assertStrength(String password, PasswordStrength expStr) {
PasswordStrength result = meter.meter(password);
assertEquals(expStr, result);
}
@Test
void meetsAllCriteria_Then_Strong() {
this.assertStrength("ab12!@AB", PasswordStrength.STRONG);
this.assertStrength("abc1!Add", PasswordStrength.STRONG);
}
}
모든 테스트 통과 완료 후
- 모든 테스트 메서드에 대해서 구현코드를 작성하고 테스트를 통과하면 src/test/java 소스폴더에 위치한 구현 클래스를 src/main/java 소스폴더로 이동한다.
TDD 흐름
Red - Green - Refactor
지금까지 작성한 코드 순서 번호 옆에 Red, Green, Refactor를 작성해놓았다.
각 단계에 대한 의미는 아래와 같다.
- Red: 실패하는 테스트
- Green: 성공한 테스트
- Refactor: 리팩토링 과정
TDD는 다음과 같은 흐름으로 진행된다. (Red)먼저 기능을 검증하는 테스트를 작성한다. (Green)그 다음, 테스트를 통과할 만큼만 코드를 작성한다. (Refactor)마지막으로 개선할 코드가 있으면 리팩토링을 한다.
이 과정을 반복하면서 점진적으로 기능을 완성해 나가는 것이 전형적인 TDD의 흐름이다.
TDD를 사용함으로써 얻을 수 있는 이점
- 위에서 본 것 처럼 개발 과정에서 지속적으로 리팩토링을 하므로 코드 품질이 급격히 나빠지지 않게 막아 주는 효과가 있다.
- 수정에 대한 피드백이 빠르다.
- 코드를 추가하거나 기존 코드를 수정하면 테스트를 돌려서 해당 코드가 올바른지 바로 확인할 수 있다.
- 구현 후 테스트 코드를 작성한다면 이런 이점을 누리기 힘들다.
위에까지가 책에 기재된 TDD의 이점(장점)들이다.
아직 본격적으로 현업에 적용해보지는 않았지만, 내가 생각하는 또다른 TDD의 장점은 개발 시간이 단축될 수 있다는 점이다.
물론 구현 후 테스트코드를 작성한다는 전제가 있어야한다. 구현한 뒤에 테스트 코드를 작성하게 되면 구현 코드에 맞는 테스트를 작성해야 되기 때문에 테스트 코드 작성이 또 다른 구현이 될 수 있을 것 같다. 반면에 TDD같은 경우에는 테스트 코드를 작성하면서 검증과 함께 구현을 같이하기에 이런 문제가 좀 덜하지 않을까 하는 의견이다.
'Spring > 테스트' 카테고리의 다른 글
| [테스트 주도 개발 시작하기] 부록C. Mockito 기초 사용법 (0) | 2024.09.09 |
|---|---|
| [테스트 주도 개발 시작하기] Chap05 JUnit5 기초 (3) | 2024.09.03 |
| [테스트 주도 개발 시작하기] Chap04 TDD-기능 명세-설계 (0) | 2024.09.01 |
| [테스트 주도 개발 시작하기] Chap03 테스트 코드 작성 순서 (1) | 2024.09.01 |