본문 바로가기
Engineering

[테스트 주도 개발 시작하기] 1 ~ 2장

by unknownomad 2023. 6. 13.

<1장> TDD 개발 준비

 

TDD

  • Test-Driven Development
  • 테스트 주도 개발

 

IDE 내에서 테스트 실행

public class StringTest {

    @Test
    void substring() {
        String str = "abcde";
        assertEquals("cd", str.substring(2, 4));
    }
}

 

터미널에서 테스트 실행

# mvn command
$ mvn test # maven test execution
$ mvnw # maven wrapper

# gradle command
$ gradle test # gradle test execution
$ gradlew # gradle wrapper

 


 

<2장> TDD 시작

 

TDD

  • 테스트 ➡️ 구현
  • 테스트를 먼저 한다 = 기능이 올바르게 작동하는지 검증하는 테스트 코드 작성

 

Assertions.assertEquals()

  • 두 값이 같은지 비교하는 단언(assertion)
  • 단언 : 값이 특정 조건을 충족하는지 확인 후, 충족하지 않으면 예외 발생

 

e.g. 1

/src/test/java/

  • 해당 경로는 배포 대상이 아니기에 미완성 코드가 배포되는 것을 방지하는 효과 있음
public class CalculatorTest {

    @Test
    void plus() {
        int result = Calculator.plus(1, 2);
        assertEquals(3, result);
    }
}

 

/src/main/java/

public class Calculator {
    // 현재 덧셈 기능 위해 새 객체를 만들 필요가 없기에
    // 정적 메서드로 구현(인스턴스 메서드 x)
    public static int plus(int a1, int a2) {
        return a1 + a2;
    }
}

 

e.g. 2

예시의 검사 규칙

  • 길이 8글자 이상 / 0 ~ 9 사이의 숫자 포함 / 대문자 포함
  • 세 규칙 모두 충족 시 암호 : 강함
  • 2개 규칙 충족 시 암호 : 보통
  • 1개 이하 규칙 충족 시 암호 : 약함

 

/src/test/java/

// 테스트 코드도 코드이기에 유지보수 대상
public class PasswordStrengthMeterTest {

    // 공통 부분 추출
    private PasswordStrengthMeter meter = new PasswordStrengthMeter();
    
    private void assertStrength(String password, PasswordStrength expStr) {
        PasswordStrength result = meter.meter(password);
        assertEquals(expStr, result);
    }
    
    // 각 조건 별 테스트
    @Test
    void meetsAllCriteria_Then_Strong() {    
        assertStrength("ab12!@AB", PasswordStrength.STRONG);
        assertStrength("abc1!Add", PasswordStrength.STRONG);
    }
    
    @Test
    void meetsOtherCriteria_except_for_Length_Then_Normal() {
        assertStrength("ab12!@A", PasswordStrength.NORMAL);
    }
    
    @Test
    void meetsOtherCriteria_except_for_number_Then_Normal() {
        assertStrength("ab!@ABqwer", PasswordStrength.NORMAL);
    }
    
    @Test
    void nullInput_Then_Invalid() {
        assertStrength(null, PasswordStrength.INVALID);
    }
    
    @Test
    void meetsOtherCriteria_except_for_Uppercase_Then_Normal() {
        assertStrength("ab12!@df", PasswordStrength.NORMAL);
    }
    
    @Test
    void meetsOnlyLengthCriteria_Then_Weak() {
        assertStrength("abdefghi", PasswordStrength.WEAK);
    }
    
    @Test
    void meetsOnlyNumCriteria_Then_Weak() {
        assertStrength("12345", PasswordStrength.WEAK);
    }
    
    @Test
    void meetsOnlyUpperCriteria_Then_Weak() {
        assertStrength("ABZEF", PasswordStrength.WEAK);
    }
    
    @Test
    void meetsNoCriteria_Then_Weak() {
        assertStrength("abc", PasswordStrength.WEAK);
    }
}

 

 

/src/main/java/

public enum PasswordStrength {
    INVALID, WEAK, NORMAL, STRONG
}

public class PasswordStrengthMeter {
    public PasswordStrength meter(String s) {
        
        if(s == null || s.isEmpty()) { // if(StringUtils.isBlank(s))
            return PasswordStrength.INVALID;
        }
        
        // 개별 규칙을 검사하는 로직
        int metCounts = getMetCriteriaCounts(s);
        
        // 규칙을 검사한 결과에 따라 암호 강도를 계산하는 로직
        if(metCounts <= 1) {
            return PasswordStrength.WEAK;
        }
        if(metCounts == 2) {
            return PasswordStrength.NORMAL;
        }
        return PasswordStrength.STRONG;
    }
    
    private int getMetCriteriaCounts(String s) {
        int metCounts = 0;
        if(s.length() >= 8) {
            metCounts++;
        }
        if(meetsContainingNumberCriteria(s)) {
            metCounts++;
        }
        if(meetsContainingUpperCriteria(s)) {
            metCounts++;
        }
        return metCounts;
    }
    
    private boolean meetsContainingNumberCriteria(String s) {
        for(char ch : s.toCharArray()) {
            if('0' <= ch && ch <= '9') {
                return true; 
            }
        }
        return false;
    }
    
    private boolean meetsContainingUpperCriteria(String s) {
        for(char ch : s.toCharArray()) {
            if(Character.isUpperCase(ch)) {
                return true;
            }
        }
        return false;
    }
}

 

TDD 흐름

 

TDD 사이클

  • 레드 - 그린 - 리팩터
  • 레드 = 실패하는 테스트
  • 그린 = 성공한 테스트 = 코드 구현해 실패 테스트를 통과시킴
  • 리팩터 = 리팩토링

 

TDD 장점

  • 테스트가 개발을 주도
  • 지속적인 코드 정리
  • 빠른 피드백

댓글