클린코드를 위한 TDD, 리팩토링 with Java 리뷰를 진행하면서 테스트만 위한 코드 작성에 관한 질문을 받았다. 테스트를 위한 코드 변경을 암묵적으로 지양하고 있었지만, 이번 기회에 다시 한번 내 생각을 정리해보았다.
public class Car {
private int position;
public Car() {
this.position = 0;
}
public Car(int position) { // 테스트를 위한 생성자
this.position = position;
}
}
@Test
@DisplayName
void findWinners() {
Car car = new Car(0);
Car car2 = new Car(1);
Cars = Cars.of(car, car2);
List<Car> winner = Cars.findWinners();
assertThat(winner).contains(car2)
}
위와 같이 position을 입력받는 생성자를 생성해두면 테스트 코드 작성은 단순해진다.
하지만 비지니스에서는 아무 쓰임 없는 코드 때문에 최초 위치가 다른 자동차의 쓰임에 대해서 고민해야 하는 상황에 부닥친다.
즉, 도메인 클래스가 비지니스 요구사항을 담아내는 설계도라고 하면 코드만 보아도 비지니스를 이해할 수 있어야 하는데 그것을 방해하는 상황이다.
@Test
@DisplayName
void findWinners() {
//as - is
Car car = new Car(1);
//to - be
Car car = new Car();
car.move(); // 자동차를 한칸 전진시킨다.
...
}
자동차를 이동시키는 메소드를 활용하여 테스트 픽스쳐를 생성하였다. 아무런 트릭없이 순수하게 테스트 픽스쳐를 생성하였지만 비지니스가 복잡해질수록 픽스쳐를 생성하는 로직이 복잡해질 수 있다.
ObjectMapper mapper = new ObjectMapper();
// setter/getter 를 만들지 않기 위해 field에 직접 접근한다.
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
String input = "{\"position\": 1}";
Car car = mapper.readValue(json, Car.class);
리플렉션을 직접 구현할 수도 있지만 흔히 사용하는 jackson library 의 ObjectMapper를 이용하여 테스트 픽스쳐를 생성하였다. 올바른 객체 생성 방법을 따르지 않았기 때문에 유효성 검증이 동작하지 않는 점이 장점이자 단점이 될 수 있고, json 파일 혹은 json string을 별도로 관리해줘야 하는 부분이 번거롭다.
public class Car {
private int position;
Car(int position) { // 접근제한자를 변경
this.position = position;
}
}
default 접근제어자를 활용 활용하면 동일 패키지 내에서만 접근할 수 있게 되어 영향을 최소화할 수 있지만 여전히 비지니스 모델과 구현 스펙이 일치하지 않는 문제를 해결할 수 없다.
테스트만을 위해 코드를 변경하는 것은 비지니스 모델과 구현 스펙에 차이를 만들기 때문에 지양 하는 것이 좋다고 생각한다. 다만 위처럼 비지니스와 유효성 검증 로직이 복잡할수록 테스트 픽스쳐를 만들기 위해서 훨씬 큰 노력이 필요하다. 이는 테스트를 꺼리게 되는 원인이 될 수 있으므로 비즈니스와 시스템의 크기를 고려하여 합리적인 판단을 할 수 있도록 하자.