랜덤에 대한 테스트

Hyun·2023년 3월 10일
1

공부한거 정리

목록 보기
7/20

코드스쿼드 Honux의 TDD 수업을 듣고

  • 항상 나는 고민이 있었다.

  • 랜덤한 값을 발생시키는 애플리케이션의 경우, 테스트를 어떻게 해야하는가?

  • 오늘 Honux 의 테스트코드 수업을 듣고 큰 깨달음을 얻어 이렇게 정리한다.


주사위 애플리케이션

  • 주사위로 홀짝 게임을 만들려 한다.
import java.util.Random;

public class Dice {

    private final Random random = new Random();

    public int roll() {
        return random.nextInt(7);
    }
}

Dice 클래스

  • 주사위를 던지는 roll() 은 1 ~ 6 범위의 랜덤 정수를 반환한다.

  • 이 경우, roll() 메서드에 대한 테스트는 다음과 같이 작성할 수 있을 것이다.
class DiceTest {

    @Test
    @DisplayName("주사위를 굴리면 1 ~ 6 사이의 숫자가 나온다.")
    void roll() {
        Dice dice = new Dice();

        assertThat(dice.roll()).isLessThanOrEqualTo(6);
        assertThat(dice.roll()).isGreaterThanOrEqualTo(1);
    }
}

숫자 범위가 1 ~ 6 사이인지 테스트할 수 있다.


  • 주사위의 홀짝을 판별하기 위한 Game 클래스를 만든다.
public class Game {

    private final Dice dice;

    public Game(Dice dice) {
        this.dice = dice;
    }

    public boolean isEven() {
        return dice.roll() % 2 == 0;
    }
}

Game 클래스

  • 주사위값이 짝수인 경우, true 를 반환하는 isEven() 메서드 구현

isEven( ) 테스트

isEven( ) 출력으로 테스트하기

  • isEven() 메서드는 dice.roll() 의 결과값에 의존한다.

  • 이때 dice.roll() 은 결과값을 예측할 수 없는 랜덤한 요소이다.

  • 테스트코드는 isEven() 의 실제 결과값과 내가 예측한 결과값을 비교하여야 하는데,

    • 내가 결과값을 예측할 수 없다.
class GameTest {

    @Test
    @DisplayName("주사위 값이 짝수인 경우 true를 반환하는 지 확인하고 싶은데...")
    void isEvenBadTest() {

        Game game = new Game(new Dice());

        for (int i = 0; i < 100; i++) {
            System.out.println(game.isEven());
        }
    }
}
  • 이렇게 눈으로 결과를 확인하는 코드는 테스트코드가 아니다.
  • 심지어 주사위값이 1 ~ 6 중 무엇인지조차 알 수 없다.
  • 그래서 isEven() 메서드가 잘 만들어졌는지 확인할 수 없다.
true
false
true
true
false
true
false
true
true
false
true
false
false
true
...

실행 결과


Mock 객체를 사용하여 isEven( ) 테스트 하기

  • 실제 객체로 테스트하기 어려운 경우가 있다.

    • 랜덤 요소, DB, 네트워크와 같이 외부에 의존하는 경우
  • 이런 경우에 예측 가능한 가짜 객체를 만들어서 테스트를 수행할 수 있다.

class GameTest {

    @Test
    @DisplayName("주사위 값이 짝수인 경우 true를 반환한다.")
    void isEven() {
        Game game = new Game(new Dice() {
            Random random = new Random();

            @Override
            public int roll() {
                return (random.nextInt(3) + 1) * 2;		// 2, 4, 6 만 반환하는 가짜 주사위 객체
            }
        });

        assertThat(game.isEven()).isTrue();
    }

    @Test
    @DisplayName("주사위 값이 홀수인 경우 false를 반환한다.")
    void isOdd() {
        Game game = new Game(new Dice() {
            Random random = new Random();

            @Override
            public int roll() {
                return (random.nextInt(3) + 1) * 2 - 1;	// 1, 3, 5 만 반환하는 가짜 주사위 객체
            }
        });

        assertThat(game.isEven()).isFalse();
    }
}

정상적인 테스트코드

  • 기존의 Dice 객체를 상속하는 가짜 주사위 객체를 만들어 테스트를 진행한다.
    • 가짜 주사위 객체는 메서드가 정상적으로 동작하는지 확인하기 위해 내가 예측가능한 값을 반환해야 한다.
    • Game 클래스에서 dice.roll() 을 호출하면, 실제 인스턴스인 가짜 주사위 객체의 roll() 이 호출된다.

출처 : 코드스쿼드 마스터즈 코스 - 백엔드 마스터 Honux 수업

0개의 댓글