"테스트 주도 개발 시작하기" - 최범균 저
위 책으로 공부하며 배운 것을 정리한 시리즈
TDD를 공부하며 1장에서(1장은 포스팅하지 않았다.) 암호 강도 측정 기능을 만들었다. 기능을 구현할 때 규칙은 다음과 같았다.
검사할 규칙 3가지
- 길이가 8글자 이상이어야 한다.
- 0부터 9 사이의 숫자를 하나 이상 포함해야 한다.
- 대문자를 하나 이상 포함해야 한다.
판별 기준
- 세 규칙을 모두 충족하면 암호 강도는 "강함"이다.
- 두개의 규칙을 충족하면 암호 강도는 "보통"이다.
- 1개 이하의 규칙을 충족하면 암호는 "약함"이다.
위 요구사항을 만족하는 테스트 코드를 작성한 순서는 다음과 같았다.
- 모든 규칙을 충족하는 암호 강도는 "강함" 이다.
- 길이만 8글자 미만이고 나머지 규칙은 충족하는 암호의 강도는 "보통" 이다.
- 숫자를 포함하지 않고 나머지 규칙은 충족하는 암호의 강도는 "보통" 이다.
- 값이 없는 암호의 강도는 "유효하지 않음" 이다.
- 대문자를 포함하지 않고 나머지 규칙은 충족하는 암호의 강도는 "보통" 이다.
- 길이가 8글자 이상인 규칙만 충족하는 암호의 강도는 "약함" 이다.
- 숫자 포함 규칙만 충족하는 암호의 강도는 "약함" 이다.
- 대문자 포함 규칙만 충족하는 암호의 강도는 "약함" 이다.
- 아무 규칙도 충족하지 않는 암호의 강도는 "약함" 이다.
위 순서는 다음 규칙에 따라 나온다.
- 쉬운 경우에서 어려운 경우로 진행
- 예외적인 경우에서 정상인 경우로 진행
반대로 어려운 경우를 먼저 시작하거나 정상 상황을 먼저 시작하면 구현 과정이 원활하게 진행 되지 않기도 한다. 왜 그럴까?
초반에 복잡한 테스트부터 시작하면 안 되는 이유
만약 초반부터 다양한 조합을 검사하는 복잡한 상황을 테스트로 추가하면, 해당 테스트를 통과하기 위해 한 번에 구현해야 할 코드가 많아진다. 예를 들어 다음 순서로 테스트를 만들었다고 하자.
- 대문자 포함 규칙만 충족하는 경우
- 모든 규칙을 충족하는 경우
- 숫자를 포함하지 않고 나머지 규칙은 충족하는 경우
먼저, 대문자 포함 규칙만 충족하는 경우를 테스트하기 위한 코드는 다음과 같이 작성할 수 있다.
@Test
void passOnlyUppercaseThenWeak(){
PasswordStrengthCheck check = new PasswordStrengthCheck();
PasswordStrength result = check.check("abcDef");
assertEquals(PasswordStrength.WEAK, result);
}
이 테스트를 통과시킬 만큼 구현 하려면, 단순히 WEAK를 리턴하면 된다.
public class PasswordStrengthCheck {
public PasswordStrength check(String s) {
return PasswordStrength.WEAK;
}
}
이제 모든 규칙을 충족하는 경우를 테스트하기 위한 코드를 추가한다.
@Test
void passAllReqThenStrong(){
PasswordStrengthCheck check = new PasswordStrengthCheck();
PasswordStrength result = check.check("abcDef12");
assertEquals(PasswordStrength.STRONG, result);
}
이 테스트를 통과시킬려면? "abcDef12" 값이면 아래처럼 STRONG을 리턴하게 해야 할까?
public class PasswordStrengthCheck {
public PasswordStrength check(String s) {
if("abcDef12".equals(s)) return PasswordStrength.STRONG;
return PasswordStrength.WEAK;
}
}
그렇다면 또 다른 값이 들어온다면? 예를 들어 "abcDef12" 가 아닌 "dEf21ab"가 들어온다면? 다른 값이 들어올 때마다 if문을 추가해야 할까? 그럴 순 없다. 좀 더 범용적인 구현이 필요하다. 범용적인 구현은 어떤 것인가? 8글자 이상이면서 대문자와 숫자가 1개 이상 있는 조건을 만들어야 하지 않을까?
그런데 그 다음에는 또 다음 규칙을 위한 구현을 해야 하지 않나? 머리가 아파온다.
한 번에 완벽한 코드를 만들면 좋겠지만, 아쉽게도 모두가 슈퍼 개발자는 아니다. 보통의 개발자는 한 번에 많은 코드를 만들다 보면 자기도 모르게 버그를 만들고, 나중에 버그를 잡기 위해 많은 시간을 허비하게 된다.
구현하기 쉬운 테스트부터 시작하기
가장 구현하기 쉬운 경우부터 시작하면 빠르게 테스트를 통과시킬 수 있다. 보통 수 분에서 십여 분 이내에 구현을 완료해서 테스트를 통과시킬 수 있을 만큼 쉬운 것을 선택한다. 위 예제에서는 다음 중 하나가 쉬울 것 같다.
- 모든 조건을 충족하는 경우
- 모든 조건을 충족하지 않는 경우
두 가지 모두 그냥 해당 값을 리턴하면 된다. 첫 번째라면 STRONG, 두 번째라면 WEAK을 리턴하면 된다.
public class PasswordStrengthCheck {
public PasswordStrength check(String s){
return PasswordStrength.STRONG;
}
}
@Test
void passAllReqThenStrong(){
PasswordStrengthCheck check = new PasswordStrengthCheck();
PasswordStrength result = check.check("ab12!@AB");
assertEquals(PasswordStrength.STRONG, result);
}
그 다음은 무엇으로 할까? 어떤 것이 구현하기 쉬울까?
- 모든 규칙을 충족하지 않는 경우
- 한 규칙만 충족하는 경우
- 두 규칙을 충족하는 경우
모든 규칙을 충족하지 않는 경우는 지금 구현하기는 어렵다. 앞서서 모든 규칙을 충족하는 경우를 테스트했는데, 이어서 정반대 조건을 구현하려면 결국 모든 규칙을 검사하는 코드를 만들어야 할 것 같다. 한 번에 구현할 것이 너무 많아진다.
한 규칙만 충족하는 경우는, 한 규칙을 검사해서 WEAK을 리턴하면 된다. 두 규칙을 충족하는 경우에도, 충족하지 않는 한 규칙을 검사해서 충족하지 않으면 NORMAL을 리턴하면 된다.
그렇다면, 모든 규칙을 충족하는 경우보다 한 규칙 또는 두 규칙을 충족하는 경우가 더 쉽게 구현할 것이라 예상한다. 그러면 여러 규칙 중에 어떤 규칙이 구현하기 가장 쉬울까? 아마 8글자 이상인지 검사하는 게 쉬울 것 같다. 그래서 다음 테스트를 두 번째로 선택한다.
- 길이만 8글자 미만이고 나머지 규칙은 충족하는 암호의 강도는 "보통"이다.
물론 다음 테스트를 두 번째로 선택해도 구현 난이도는 비슷하게 쉽다.
- 길이가 8글자 이상이고 나머지 규칙은 충족하지 않는 암호의 강도는 "약함"이다.
둘 다 길이가 8글자 이상인지 여부를 판단하는 로직만 구현하면 된다.
한 번에 구현하는 시간이 짧아지면 디버깅할 때에 유리하다. 작성한 코드가 많지 않고 작성 시간도 짧으면 머릿속에 코드에 대한 내용이 생생하게 남아 있으므로, 디버깅할 때 문제의 원인을 빠르게 찾을 수 있다.
'Book > 테스트 주도 개발 시작하기' 카테고리의 다른 글
TDD - 테스트 작성 순서 연습(1) (0) | 2023.12.20 |
---|---|
TDD - 테스트 코드 작성 순서 (2) (1) | 2023.12.20 |