본문 바로가기
Test Code

[JUnit] 테스트하기 어려운 코드를 테스트 하기 쉬운 코드로

by bkuk 2023. 4. 22.

테스트하기 어려운 코드

package racing;

public class Car {
    private static final int FORWARD_NUM = 4;
    private static final int MAX_BOUND = 10;
    private String name;
    private int position;
    
    public void move() {
    	if( getRandomNo() >= FORWARD_NUM)
        	this.position++;
	}
    private int getRandomNo() {
    	Random random = new Random();
        return random.nextInt(MAX_BOUND);
    }
    //생략..
}

 

Car 라는 클래스는 Car의 이름을 의미하는 멤버변수 'name'Car의 위치를 의미하는 멤버변수 'position'이 존재한다. move() 메서드 호출 시 Random 클래스를 통해서 생성된 정수 0 ~ 10 사이의 무작위 숫자를 통해 해당 객체의 멤버 변수 position이 1이 증가하느냐, 증가하지 않느냐가 결정된다.

즉, move() 메서드의 결과는 예측할 수 없으므로, 테스트하기 어려운 코드이다. 

아래 테스트 코드를 살펴보자.

 

pass 하거나 fail 하거나

@Test
void 자동차_전진() {
    Car car = new Car("ukis");
    car.move(4);
    assertThat(car.getPosition()).isEqualTo(1);
}

@Test
void 자동차_정지() {
    Car car = new Car("ukis");
    car.move(3);
    assertThat(car.getPosition()).isEqualTo(0);
}

위 테스트 코드를 실행해보면, 테스트가 성공하거나 실패하거나.. 상황에 따라 달라진다.

테스트 가능한 코드로 개선이 필요하다.

어떻게 개선할까?

 

변경하려고 했지만, 의존관계를 가지는 곳이 많다면?

단, move() 메서드에 대한 의존관계를 가지고 있는(혹은 사용하고 있는) 곳이 굉장히 많다고(1억..?) 가정해보자.

왜냐하면, move() 메서드의 메서드 시그니처를 변경한다면 의존관계를 가지고 있는 곳을 모두 다 수정해 줘야하는 상황이 발생하기 때문이다.

이 상황은 레거시 코드를 리팩토링하는 상황에서 빈번하게 발생할 수 있다.

그렇다면, 메서드 시그니처를 바꾸지 않고 이를 어떻게 개선할 수 있을까?

 

메서드 시그니처(Method Signature)가 아닌 접근제어자(access modifier)를 변경해보자.

우선, 문제가 되는 코드를 살펴보자.

    public void move() {
    	if( getRandomNo() >= FORWARD_NUM)
        	this.position++;
	}
    private int getRandomNo() {
    	Random random = new Random();
        return random.nextInt(MAX_BOUND);
    }

테스트하기 어려운 코드는 getRandomNo() 이며, 랜덤값을 생성하는 메서드이다. 개발자가 랜덤값을 제어할 수 없다.

테스트가 가능할려면, random 값을 제어할 수 있어야한다. 

따라서 접근제어자(access modifier)를 'protected'로 변경해보자.

    protected int getRandomNo() {
    	Random random = new Random();
        return random.nextInt(MAX_BOUND);
    }

 

자식 클래스에서 메서드를 Overrid를 통해 테스트 코드를 완성시켜보자. 

    @Test
    void 자동차_전진_레거시_리팩토링() {
        Car car = new Car("pobi") {
            @Override
            protected int getRandomNo() {
                return 4;
            }
        };
        car.move();
        assertThat(car.getPosition()).isEqualTo(1);
    }
    
    @Test
    void 자동차_정지_레거시_리팩토링() {
        Car car = new Car("pobi") {
            @Override
            protected int getRandomNo() {
                return 3;
            }
        };
        car.move();
        assertThat(car.getPosition()).isEqualTo(0);
    }

 

추후 장기적으로는 리팩토링을 통해서 아래와 같은 구조를 만들어보자.

    public void move(int number) {
        if( isMove(number) ) {
            this.position += 1;
        }
    }
    
    private boolean isMove(int number) {
        return number >= MIN_FORWARD_NUMBER;
    }

 

댓글