테스트 코드를 작성하다 보면 랜덤 값에 의해 다르게 작동하는 메서드를 테스트해야 하는 경우가 있다.
예를 들어보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Game {
private int position;
public void move() {
if (canMove()) {
position++;
}
}
private boolean canMove() {
return 5 < RandomNumberGenerator.generate(0,9);
}
}
위 게임은 0~9까지의 랜덤값을 뽑고 그 수가 5보다 크다면 position을 1씩 증가시킬 수 있는 게임이다.
이 때 move() 메서드에서 랜덤 값에 의해 position의 변화가 이루어지기 때문에 테스트를 진행하는데 어려움이 생긴다. 값이 랜덤이면 move()에 의해 position에 변동이 있는지, 없는지 알 수 없기 때문이다.
이렇게 되면 테스트는 어쩔때는 성공하고, 실패하게 되는 것이다.
랜덤값에 의한 메서드 테스트 해결방법
RandomNumberGenerator와 Game은 지금 강하게 결합되어 있다. 이 결합을 분리하여 테스트를 진행할 수 있도록 수정하면 된다.
- RandomUtil 인터페이스를 만든다.
- 랜덤값을 생성하는 객체(RandomNumberGenerator)를 RandomUtil의 구현체로 만든다.
- 이 구현체를 Game은 주입받아 사용하도록 수정한다.
인터페이스 생성
1
2
3
public interface RandomUtil {
int generate(int min, int max);
}
참고로 인터페이스를 생성해야 아래 테스트 코드의 람다식 형태를 사용할 수 있다.
구현체로 하면 new RandomNumberGenerator()를 넣어주어야 해 람다식 형태는 사용할 수 없다.
RandomNumberGenerator 구현체 생성
1
2
3
4
5
6
7
public class RandomNumberGenerator implements RandomUtil {
@Override
public int generate(int min, int max) {
return Randoms.pickNumberInRange(min, max);
//min, max 범위 내에서 랜덤값을 추출한다.
}
}
Game이 주입받아 사용하도록 수정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Game {
private int position;
private RandomUtil randomUtil;
public Game(int position, RandomUtil randomutil) {
this.position = position;
this.randomUtil = randomUtil;
}
public void move() {
if (canMove()) {
position++;
}
}
private boolean canMove() {
return 5 < randomUtil.generate(0,9);
}
}
테스트 코드
위와 같은 수정을 통해 랜덤값의 값을 고정시켜 테스트할 수 있게 된다.
1
2
3
4
5
6
7
8
@Test
public void moveTest() {
Game game = new Game(1, (min, max) -> 6);
game.move();
assertThat(game).extracting("position").isEqualTo(2);
}
(min,max) -> 6 은 RandomUtil 인터페이스의 generate 메서드를 구현한 람다 표현식이 된다.
어떤 min과 max값을 전달해도 항상 6을 반환하게 된다.
랜덤 값을 6으로 지정했으므로 game의 move 메서드를 실행하면 항상 position을 1 증가시키게 되는 것이고 위의 테스트가 성공하게 되는 것이다.