1. 네 번째 테스트
값이 없는 경우
- 예외상황을 고려하지 않으면 비정상 작동
- 값이 없는 경우에 대해서 알맞은 동작이 되도록 테스트 해야함
- 값이 없는 경우에는 어떻게 작동해야 할 지 고민 필요, IllegalArgumentException 발생 시키거나, 유효하지 않은 암호를 의미하는 PasswordStrength.INVALID를 리턴하게 할 수 있음(코드는 후자의 방법으로 진행)
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("비밀번호가 Null값으로 들어온 경우")
void nullInput_Then_Invalid() {
assertStrength(null, PasswordStrength.INVALID);
}
}
-> PasswordStrength.INVALID가 없기 때문에 컴파일 에러 발생
2) PasswordStrength에 INVALID 추가
public enum PasswordStrength {
STRONG,
NORMAL,
INVALID
}
-> NullPointerException 발생
3) PasswordStrengthMeter에 null 처리 구현 추가
public class PasswordStrengthMeter {
public PasswordStrength meter(String s) {
if (s == null)
return PasswordStrength.INVALID;
if (s.length() < 8) {
return PasswordStrength.NORMAL;
}
boolean containsNum = 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) 빈 문자열에 들어오는 예외 상황 테스트 추가
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("비밀번호가 Null값으로 들어온 경우")
void nullInput_Then_Invalid() {
assertStrength(null, PasswordStrength.INVALID);
}
@Test
@DisplayName("비밀번호가 빈문자열로 들어온 경우")
void emptyInput_Then_Invalid() {
assertStrength("", PasswordStrength.INVALID);
}
}
-> 기대한 값은 INVALID이지만 NORMAL이 결과값
5) 테스트 코드를 통과시키기 위해 빈 문자열에 대한 구현 추가
public class PasswordStrengthMeter {
public PasswordStrength meter(String s) {
if (s == null || s.isEmpty()) // 빈 문자열에 대한 구현 추가
return PasswordStrength.INVALID; // PasswordStrengthMeter, null 처리 구현 추가
if (s.length() < 8) {
return PasswordStrength.NORMAL;
}
boolean containsNum = 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;
}
}
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 meetsOtherCriteria_except_for_Uppercase_Then_Normal() {
assertStrength("abcd!!@d", PasswordStrength.NORMAL);
}
}
-> 테스트 실패
2) 대문자를 포함하지 않은 경우에 대한 테스트를 통과시키기 위한 코드 작성
public class PasswordStrengthMeter {
public PasswordStrength meter(String s) {
if (s == null || s.isEmpty()) // 빈 문자열에 대한 구현 추가
return PasswordStrength.INVALID; // PasswordStrengthMeter, null 처리 구현 추가
if (s.length() < 8) {
return PasswordStrength.NORMAL;
}
boolean containsNum = meetsContainingNumberCriteria(s);
if (!containsNum) return PasswordStrength.NORMAL;
// 대문자를 포함하지 않은 경우에 대한 테스트를 통과시키기 위한 코드 작성
boolean containsUpp = false;
for(char ch : s.toCharArray()) {
if(Character.isUpperCase(ch)) {
containsUpp = true;
break;
}
}
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;
}
}
-> 테스트 성공
3) 코드 리팩토링
public class PasswordStrengthMeter {
public PasswordStrength meter(String s) {
if (s == null || s.isEmpty()) // 빈 문자열에 대한 구현 추가
return PasswordStrength.INVALID; // PasswordStrengthMeter, null 처리 구현 추가
if (s.length() < 8) {
return PasswordStrength.NORMAL;
}
// 숫자를 포함하지 않은 경우에 대한 테스트를 통과시키기 위한 코드 리팩토링
boolean containsNum = meetsContainingNumberCriteria(s);
if (!containsNum) return PasswordStrength.NORMAL;
// 대문자를 포함하지 않은 경우에 대한 테스트를 통과시키기 위한 코드 리팩토링
boolean containsUpp = meetsContainingUppercaseCriteria(s);
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. 여섯 번째 테스트
길이가 8글자 이상인 조건만 충족하는 경우
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("비밀번호 강도 약함 - 길이 8자 이상 조건만 충족")
void meetsOnlyLengthCriteria_Then_Weak() {
assertStrength("abcdefghi", PasswordStrength.WEAK);
}
}
-> PasswordStrength.WEAK가 존재하지 않기 때문에 컴파일 에러 발생
2) 컴파일 에러를 없애기 위한 코드 수정
public enum PasswordStrength {
STRONG,
NORMAL,
WEAK,
INVALID
}
-> 코드 추가 후 테스트하면 테스트 실패, 테스트 결과가 WEAK이 나와야 하는데 NORMAL이 나옴
-> 테스트 통과를 위한 고민 하기
- 3개의 조건 중 8자 이상의 길이를 충족하고, 나머지 2개 조건은 충족하지 않을 때 WEAK을 리턴해야 함
- 3개의 조건을 모두 검사 후 그 결과에 따른 값을 리턴해야 함
- 위 상황을 고려했을 때 기존에 길이 검사 코드를 아래와 같이 수정
3) 코드 수정
public class PasswordStrengthMeter {
public PasswordStrength meter(String s) {
if (s == null || s.isEmpty()) // 빈 문자열에 대한 구현 추가
return PasswordStrength.INVALID; // PasswordStrengthMeter, null 처리 구현 추가
// 아래 코드로 변경
// if (s.length() < 8) {
// return PasswordStrength.NORMAL;
// }
// 길이가 8자 미만일 경우에 대한 테스트를 통과시키기 위한 코드 리팩토링
boolean lengthEnough = s.length() >= 8;
if (!lengthEnough) return PasswordStrength.NORMAL;
// 숫자를 포함하지 않은 경우에 대한 테스트를 통과시키기 위한 코드 리팩토링
boolean containsNum = meetsContainingNumberCriteria(s);
if (!containsNum) return PasswordStrength.NORMAL;
// 대문자를 포함하지 않은 경우에 대한 테스트를 통과시키기 위한 코드 리팩토링
boolean containsUpp = meetsContainingUppercaseCriteria(s);
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;
}
}
4) 코드 리팩토링
- if 절의 위치를 이동 시킴
왜? 크게 두 개의 로직(개별 규칙을 검사하는 로직, 규칙 검사 결과에 따른 암호 강도 계산 로직)으로 구분해서 모으기 위함
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);
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;
}
}
-> 아직 테스트가 성공되는 코드는 구현하지 않음, 테스트 성공을 위한 코드 구성을 갖춤
5) 길이가 8이상인 조건만 충족하는 경우를 통과시키기 위한 코드 추가
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) 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;
}
}
-> 테스트 실행 통과
'Spring > Test' 카테고리의 다른 글
TDD 테스트 코드 작성 방법 (0) | 2022.11.27 |
---|---|
테스트 주도 개발 시작하기 - TDD 시작, TDD 암호 검사기 3 (0) | 2022.11.27 |
테스트 주도 개발 시작하기 - TDD 시작, TDD 암호 검사기 1 (0) | 2022.11.27 |
테스트 주도 개발 시작하기 - TDD 시작, TDD란 (0) | 2022.11.27 |
📝 테스트 코드 작성의 장/단점과 테스트 범위에 따른 분류 (0) | 2022.11.18 |