본문 바로가기

Spring/Test

테스트 주도 개발 시작하기 - TDD 시작, TDD 암호 검사기 3

1. 일곱 번째 테스트

숫자 포함 조건만 충족하는 경우

 

1) 테스트 코드 작성

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class PasswordStrengthMeterTest {
    private PasswordStrengthMeter meter = new PasswordStrengthMeter(); // 중복되는 코드, 필드에서 생성하도록 수정

    private void assertStrength(String password, PasswordStrength expStr) { // 중복되는 코드, 메소드를 이용해서 제거
        PasswordStrength result = meter.meter(password);
        assertEquals(expStr, result);
    }

// 생략

    @Test
    @DisplayName("비밀번호 강도 약함 - 숫자 포함 조건만 충족")
    void meetsOnlyNumCriteria_Then_Weak() {
        assertStrength("12345", PasswordStrength.WEAK);
    }

}

 

2) 숫자 포함 조건만 충족하는 경우를 통과시키기 위한 코드 추가

public class PasswordStrengthMeter {

    public PasswordStrength meter(String s) {
        if (s == null || s.isEmpty()) //  null, empty 처리를 위한 구현
            return PasswordStrength.INVALID;

        // 길이가 8자 이상인 경우 true
        boolean lengthEnough = s.length() >= 8;
        // 0~9 범위의 숫자가 있는 경우 true
        boolean containsNum = meetsContainingNumberCriteria(s);
        // 대문자를 포함하는 경우 true
        boolean containsUpp =  meetsContainingUppercaseCriteria(s);

        // 길이가 8이상인 조건만 충족하는 경우를 통과시키기 위한 코드 추가
        if (lengthEnough && !containsNum && !containsUpp) return PasswordStrength.WEAK;
        
        // 숫자 포함 조건만 충족하는 경우를 통과시키기 위한 코드 추가
        if (!lengthEnough && containsNum && !containsUpp) return PasswordStrength.WEAK;

        if (!lengthEnough) return PasswordStrength.NORMAL;
        if (!containsNum) return PasswordStrength.NORMAL;
        if (!containsUpp) 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;
    }

    // 대문자 확인 메소드 추출
    private boolean meetsContainingUppercaseCriteria(String s) {
        for(char ch : s.toCharArray()) {
            if(Character.isUpperCase(ch)) {
                return true;
            }
        }
        return false;
    }

}

-> 테스트 코드 실행 통과

 

2. 여덟 번째 테스트

대문자 포함 조건만 충족하는 경우

 

1) 테스트 코드 작성

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class PasswordStrengthMeterTest {
    private PasswordStrengthMeter meter = new PasswordStrengthMeter(); // 중복되는 코드, 필드에서 생성하도록 수정

    private void assertStrength(String password, PasswordStrength expStr) { // 중복되는 코드, 메소드를 이용해서 제거
        PasswordStrength result = meter.meter(password);
        assertEquals(expStr, result);
    }

// 생략

    @Test
    @DisplayName("비밀번호 강도 약함 - 대문자 포함 조건만 충족")
    void meetsOnlyUpperCriteria_Then_Weak() {
        assertStrength("ABCD", PasswordStrength.WEAK);
    }

}

 

2) 대문자 포함 조건만 충족하는 경우를 통과시키기 위한 코드 추가 

public class PasswordStrengthMeter {

    public PasswordStrength meter(String s) {
        if (s == null || s.isEmpty()) //  null, empty 처리를 위한 구현
            return PasswordStrength.INVALID;

        // 길이가 8자 이상인 경우 true
        boolean lengthEnough = s.length() >= 8;
        // 0~9 범위의 숫자가 있는 경우 true
        boolean containsNum = meetsContainingNumberCriteria(s);
        // 대문자를 포함하는 경우 true
        boolean containsUpp =  meetsContainingUppercaseCriteria(s);

        // 길이가 8이상인 조건만 충족하는 경우를 통과시키기 위한 코드 추가
        if (lengthEnough && !containsNum && !containsUpp) return PasswordStrength.WEAK;

        // 숫자 포함 조건만 충족하는 경우를 통과시키기 위한 코드 추가
        if (!lengthEnough && containsNum && !containsUpp) return PasswordStrength.WEAK;

        // 대문자 포함 조건만 충족하는 경우를 통과시키기 위한 코드 추가
        if (!lengthEnough && !containsNum && containsUpp) return PasswordStrength.WEAK;

        if (!lengthEnough) return PasswordStrength.NORMAL;
        if (!containsNum) return PasswordStrength.NORMAL;
        if (!containsUpp) 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;
    }

    // 대문자 확인 메소드 추출
    private boolean meetsContainingUppercaseCriteria(String s) {
        for(char ch : s.toCharArray()) {
            if(Character.isUpperCase(ch)) {
                return true;
            }
        }
        return false;
    }

}

-> 테스트 코드 실행 통과

 

 

 

3. 코드정리

meter() 메소드 리팩토링

 

1) 작성된 코드를 보고 코드의 의미를 파악하여 코드 수정

public class PasswordStrengthMeter {

    public PasswordStrength meter(String s) {
        if (s == null || s.isEmpty()) //  null, empty 처리를 위한 구현
            return PasswordStrength.INVALID;
        int metCounts = 0; // 조건의 개수 카운트를 위한 변수 추가
        
        // 길이가 8자 이상인 경우 true
        boolean lengthEnough = s.length() >= 8;
        if (lengthEnough) metCounts++;
        
        // 0~9 범위의 숫자가 있는 경우 true
        boolean containsNum = meetsContainingNumberCriteria(s);
        if (containsNum) metCounts++;
        
        // 대문자를 포함하는 경우 true
        boolean containsUpp =  meetsContainingUppercaseCriteria(s);
        if (containsUpp) metCounts++;
        
        // 1개의 조건 충족 시 강도가 약함이라는 규칙을 잘 보여줌
        if (metCounts == 1) return PasswordStrength.WEAK;
        if (metCounts == 2) return PasswordStrength.NORMAL;
        
//        코드 수정 전
//        // 길이가 8이상인 조건만 충족하는 경우를 통과시키기 위한 코드 추가
//        if (lengthEnough && !containsNum && !containsUpp) return PasswordStrength.WEAK;
//
//        // 숫자 포함 조건만 충족하는 경우를 통과시키기 위한 코드 추가
//        if (!lengthEnough && containsNum && !containsUpp) return PasswordStrength.WEAK;
//
//        // 대문자 포함 조건만 충족하는 경우를 통과시키기 위한 코드 추가
//        if (!lengthEnough && !containsNum && containsUpp) return PasswordStrength.WEAK;
        
     

        return PasswordStrength.STRONG;

    }


    // 숫자 확인 메소드 추출
    private boolean meetsContainingNumberCriteria(String s) {
        for(char ch : s.toCharArray()) {
            if(ch >= '0' && ch <= '9') {
                return true;
            }
        }
        return false;
    }

    // 대문자 확인 메소드 추출
    private boolean meetsContainingUppercaseCriteria(String s) {
        for(char ch : s.toCharArray()) {
            if(Character.isUpperCase(ch)) {
                return true;
            }
        }
        return false;
    }

}

 -> 테스트 실행 통과 확인

 

 

 

2) 수정된 코드 재 수정

public class PasswordStrengthMeter {

    public PasswordStrength meter(String s) {
        if (s == null || s.isEmpty()) //  null, empty 처리를 위한 구현
            return PasswordStrength.INVALID;
        int metCounts = 0; // 조건의 개수 카운트를 위한 변수

        // 길이가 8자 이상인 경우 metConuts 1증가        
        if (s.length() >= 8) metCounts++;
        
        // 0~9 범위의 숫자가 있는 경우 metConuts 1증가
        if (meetsContainingNumberCriteria(s)) metCounts++;
        
        // 대문자를 포함하는 경우 metConuts 1증가
        if (meetsContainingUppercaseCriteria(s)) metCounts++;

        
        // 조건 충족 개수에 따른 강도가 잘 보임
        if (metCounts == 1) return PasswordStrength.WEAK;
        if (metCounts == 2) 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;
    }

    // 대문자 확인 메소드 추출
    private boolean meetsContainingUppercaseCriteria(String s) {
        for(char ch : s.toCharArray()) {
            if(Character.isUpperCase(ch)) {
                return true;
            }
        }
        return false;
    }

}

-> 테스트 코드 실행 통과

 

 

4. 아홉 번째 테스트

아무 조건도 충족하지 않은 경우

 

1) 테스트 코드 작성

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class PasswordStrengthMeterTest {
    private PasswordStrengthMeter meter = new PasswordStrengthMeter(); // 중복되는 코드, 필드에서 생성하도록 수정

    private void assertStrength(String password, PasswordStrength expStr) { // 중복되는 코드, 메소드를 이용해서 제거
        PasswordStrength result = meter.meter(password);
        assertEquals(expStr, result);
    }

// 생략

    @Test
    @DisplayName("아무 조건도 충족하지 않은 경우")
    void meetsNoCriteria_Then_Weak() {
        assertStrength("abc", PasswordStrength.WEAK);
    }

}

-> 테스트 실행 통과 실패, 예상되는 값은 WEAK인데 STRONG이 나옴

 

 

2) 아무조건도 충족하지 않은 암호를 위한 코드 구현(코드는 방법 1로 구현)

- 방법 1. 충족 개수가 1개 이하인 경우 WEAK을 반환

- 방법2. 충족 개수가 0개인 경우 WEAK을 반환

- 방법3. 충족 개수가 3개인 경우 STRONG을 반환하는 코드를 추가하고 마지막에 WEAK 반환

 

    public PasswordStrength meter(String s) {
        if (s == null || s.isEmpty()) //  null, empty 처리를 위한 구현
            return PasswordStrength.INVALID;
        int metCounts = 0; // 조건의 개수 카운트를 위한 변수

        // 길이가 8자 이상인 경우 metConuts 1증가
        if (s.length() >= 8) metCounts++;

        // 0~9 범위의 숫자가 있는 경우 metConuts 1증가
        if (meetsContainingNumberCriteria(s)) metCounts++;

        // 대문자를 포함하는 경우 metConuts 1증가
        if (meetsContainingUppercaseCriteria(s)) metCounts++;


        // 조건 충족 개수에 따른 강도가 잘 보임
        if (metCounts <= 1) return PasswordStrength.WEAK;
        if (metCounts == 2) 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;
    }

    // 대문자 확인 메소드 추출
    private boolean meetsContainingUppercaseCriteria(String s) {
        for(char ch : s.toCharArray()) {
            if(Character.isUpperCase(ch)) {
                return true;
            }
        }
        return false;
    }

}

-> 테스트 코드 실행 통과

 

 

5. 코드 정리

코드 가독성 개선

- metCounts 변수를 계산하는 부분을 메서드로 빼서 가독성을 높임

public class PasswordStrengthMeter {

    public PasswordStrength meter(String s) {
        //  null, empty 처리를 위한 구현
        if (s == null || s.isEmpty()) 
            return PasswordStrength.INVALID;
        
        // 조건 충족 개수를 계산하는 metCounts 메소드 호출
        int metCounts = getMetCriteriaCounts(s); 

        // 조건 충족 개수에 따른 강도
        if (metCounts <= 1) return PasswordStrength.WEAK;
        if (metCounts == 2) return PasswordStrength.NORMAL;

        return PasswordStrength.STRONG;

    }
    
    // metCounts 계산 메소드
    private int getMetCriteriaCounts(String s) {
        int metCounts = 0; // 조건의 개수 카운트를 위한 변수
        // 길이가 8자 이상인 경우 metConuts 1증가
        if (s.length() >= 8) metCounts++;

        // 0~9 범위의 숫자가 있는 경우 metConuts 1증가
        if (meetsContainingNumberCriteria(s)) metCounts++;

        // 대문자를 포함하는 경우 metConuts 1증가
        if (meetsContainingUppercaseCriteria(s)) metCounts++;
        
        return metCounts;
    }


    // 숫자 확인 메소드
    private boolean meetsContainingNumberCriteria(String s) {
        for(char ch : s.toCharArray()) {
            if(ch >= '0' && ch <= '9') {
                return true;
            }
        }
        return false;
    }

    // 대문자 확인 메소드
    private boolean meetsContainingUppercaseCriteria(String s) {
        for(char ch : s.toCharArray()) {
            if(Character.isUpperCase(ch)) {
                return true;
            }
        }
        return false;
    }

}

-> 코드 리팩토링을 통해 가독성이 좋아짐

-> 테스트 및 코드 리팩토링이 끝난 파일을 src/test/java에서 src/main/java로 이동하면 끝!