멱등성과 테스트의 관계
메소드 내부에서 사용하기만 해도 테스트가 어려워지는 유형의 코드가 있다.
다음의 예제를 살펴보자.
public class DifficultTest {
public int nextLottoNumber() {
Random random = new Random();
return random.nextInt(45) + 1;
}
public String todayDateToString() {
return LocalDateTime.now().toString();
}
public String inputIntToString() {
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
int b = scanner.nextInt();
return String.valueOf(a) + String.valueOf(b);
}
}
메소드 하나가 하나의 예시라고 생각하고 작성했다.
공통점은 멱등성
이 지켜지지 않는다는 코드라는 점에 있다.
테스트가 어려운 이유? 🤔
nextLottoNumber
- 로또 번호를 반환하는 메소드이다.
- nextInt()는 호출 시마다 다른 값이 반환되며 여럿 호출한다 해도 연속으로 같은 값이 나오리라는 보장이 없다.
- 기대 입력값은 없지만 기대 출력값은 범위 내 임의의 숫자로, 입출력이 1:1 매칭되지 않는다.
- 메소드 내에서
Random
을 생성하고 사용한다. 이 메소드에 숫자 생성 전략이 강하게 결합되어 있어 분리하기 쉽지 않다는 문제도 가지고 있다.
todayDateToString
LocalDateTime.now()
는 자바 8에서 도입된 이래로 현재 시간을 대입하는 용도로 많이 사용되고 있다.- 메소드 호출 시점에 따라 값이 달라지며 어떤 값이 결과로 나타날지 정확하게 테스트할 수는 없다.
inputIntToString
- 두 개의 int값을 입력받고 문자열로 합쳐 반환하는 메소드이다.
Scanner
표준 입력의 특성 상 입력받을 데이터가 많아지면 테스트하기가 어려워진다.- 같은 값을 계속 입력하면 멱등성이 아닌가 하는 생각이 들 수도 있지만, 이 코드가 사용되는 모든 케이스에서 같은 값이 계속 입력하기를 기대하기는 어렵다.
해결 방법
멱등성으로 인해 테스트가 어려워졌다면, 멱등성을 보장하면 된다.
import java.time.Clock;
import java.time.LocalDateTime;
public class DifficultTest {
public int nextLottoNumber(LottoStrategy lottoStrategy) {
return lottoStrategy.nextNumber();
}
// type 1
public String todayDateToString1(LocalDateTime localDateTime) {
return localDateTime.toString();
}
// type 2
public String todayDateToString2(Clock clock) {
return LocalDateTime.now(clock).toString();
}
public String inputIntToString(int a, int b) {
return String.valueOf(a) + String.valueOf(b);
}
}
@FunctionalInterface
public interface LottoStrategy {
int nextNumber();
}
nextLottoNumber
- 번호 생성의 책임을
LottoStrategy
으로 옮겼다. - 이제 이 메소드는 다음 번호를 뽑아낸다는 하나의 책임만을 가진다.
- 테스트를 할 때도
LottoStrategy
를 정의해서 테스트하면 된다. @Test public void test1() { DifficultTest difficultTest = new DifficultTest(); int nextNumber = 3; assertEquals(difficultTest.nextLottoNumber(() -> nextNumber), nextNumber); }
- 번호 생성의 책임을
todayDateToString
LocalDateTime
을 메소드로 주입받거나,Clock
을 주입받아서 해결할 수 있다.LocalDateTime
을 직접 주입받으면 입출력 값을 정확하게 통제할 수 있다.Clock
은 now()의 매개변수로 삽입하여 시점을 통제한다.
@Test public void test2() { DifficultTest difficultTest = new DifficultTest(); Clock clock = Clock.systemDefaultZone(); System.out.println(difficultTest.todayDateToString2(clock)); System.out.println(difficultTest.todayDateToString2(clock)); System.out.println(difficultTest.todayDateToString2(clock)); } /* 실행 결과 2020-11-08T21:36:18.795481200 2020-11-08T21:36:18.795481200 2020-11-08T21:36:18.795481200 */
inputIntToString
scanner.nextInt()
를 mock하지 않고 주입받도록 변경했다.- 테스트하기가 매우 편리해진다.
결론
멱등성이 지켜지는 코드는 테스트하기가 쉽다.
테스트 뭐 그까짓거 안해도 되지 않냐? 라고 생각할 수 있는데, 코드를 테스트하기 쉬우면 적절하게 책임을 잘 분리했다는 말도 된다.
유지보수에도 도움이 많이 될 것이다.
뿐만 아니라 멱등성은 API를 설계할 때도 고려하기에 좋은 요소이다.
멱등성이 지켜진다면 예측할 수 없는 다수의 요청이 한 번에 들어오는 상황에도 잘 대처할 수 있기 때문이다.
'Java' 카테고리의 다른 글
ATDD 아주 간단히 찍먹 (0) | 2021.07.21 |
---|---|
안전하게 코딩하는 좋은 습관, 일급 콜렉션 (1) | 2020.11.08 |
Java Stream (6) 스트림 생성 (0) | 2020.07.22 |
Java Stream (5) 기본 자료형 스트림 (0) | 2020.07.22 |
Java Stream (4) 스트림 주요 메소드 (0) | 2020.07.17 |