2022. 2. 23. 22:47ㆍJAVA
말로만 듣던 TDD(Test Driven Development)를 이번주에 처음으로 적용해봤다.
원칙대로 먼저 테스트를 작성하고 소스를 후에 작성하며 진행하며 든 가장 큰의문은
정말 private 메서드를 테스트하지 않아도 되는것인가 ?
로또 발급과 수익율 계산이라는 미션을 TDD로 수행했다.
아직 있지도 않은 메서드 목록을 상상하고, 그 시나리오를 가정해가며 세부 로직에 대한 테스트를 작성했다.
하지만, 실제 소스 작성을 진행할수록 많은 메서드가 외부에 노출되지 않는 private 메소드라 원칙에 따라 작성한 작성한 테스트를 눈물을 머금고 날려야했다.
그런데 아무리 생각해도, 핵심 로직인 것 같은 메서드가 접근 지정자가 private이라는 이유만으로 테스트하지 않는다는 것이 이해가 가지 않았다.
핵심 로직임에도 불구하고 private 메서드를 테스트하지 않아도 되는것인가 ?
아래 블럭은 1단계를 완료하고 pull request를 보낸 실제 코드의 일부분이다. (세부 작동은 가독성을 위해 지웠습니다.)
public class LottoTicket {
public void start() {
List<Lotto> lottos = makeLotto();
List<Integer> userWinningNumber = InputView.getWinningNumber();
start(lottos, userWinningNumber);
}
private void start(List<Lotto> lottos, List<Integer> userWinningNumber) {
int[] counts = makeStatistics(lottos, userWinningNumber);
double totalYield = calculateYield(counts);
OutputView.printWinningStatistics(counts, totalYield);
}
private double calculateYield(int[] counts) {
.
.
.
}
private int[] makeStatistics(List<Lotto> lottos, List<Integer> userWinningNumber) {
.
.
.
}
private List<Lotto> makeLotto() {
.
.
.
}
private List<Integer> createNumbers() {
.
.
.
}
}
사용자는 외부에서 LottoTicket 객체를 생성하고, init() 메서드를 호출한다.
그러면 내부에서 입출력, 데이터 생성, 계산까지 알아서 척척 해준다. 만능 객체다 !
이제 테스트코드를 확인해보자.
@Test
void initTest(){
.
.
.
}
@Test
void startTest(){
.
.
.
}
분명히 입출력과 데이터 생성, 계산까지 하는 객체인데, 열려있는 메서드는 2개밖에 존재하지 않아서 테스트는 2개밖에 존재하지 않는다 (물론 시나리오를 늘리면 더 늘어난다.)
물론 문제는 없다. 왜 ? 작동이 잘 되니까 !
사실 문제가 많다.
무엇이 문제일까?
1. 하나의 메소드에 너무 큰 볼륨의 테스트가 필요하다.
2. 중간 과정에 대한 검증을 할 수 없다.
3. 메서드들이 너무 강한 의존관계를 갖고있다.
4. 그래서 ! 유지보수가 힘들다 !
어쩌면 private 을 테스트하느냐 마느냐의 문제가 아닐 수도 있다 ?
메서드들이 강한 의존관계를 갖고있다는 점에 집중하자.
현재 작성한 코드는 데이터생성, 계산, 입력, 출력까지의 로직이 하나의 클래스에 몰려있다.
그래서 LottoTicket 클래스를 LottoGame, Statistics 을 생성하고 LottoTicket 에 있는 메서드를 옮겨담았다.
역할과 기능이 명확하게 분리된 것이다.
클래스를 생성해서 역할을 나눠줬을 뿐인데 외부에서 접근할 수 있는 interface가 늘어났다.
외부에서 접근하는 메서드는 당연히 테스트를 진행해야 한다.
@Test
void initTest(){}
@Test
void startTest(){}
.
.
.
@Test
void calculateYieldTest(){}
@Test
void makeStatisticsTest(){}
@Test
void makeLottoTest(){}
@Test
void createNumbersTest(){}
결과로, private 메서드를 갖은 방법을 동원해 억지로 테스트하는 것이 아니라 TDD의 원칙대로 자연스럽게 public 메서드를 테스트 하면서도 역할과 기능이 잘 분리된 프로그램을 작성할 수 있게 되었다.
물론 private 메서드 테스트의 필요가 느껴진다고 해서 무리해서 클래스를 분리할 필요는 없다.
만약 상위 메소드 A()라는 의미 안에 하위 메소드 B()가 모두 포함되어 있다면 B는 A를 통해 테스트할 수 있다고 가정한다.
결론
TDD든 BDD, DDD 할아버지든 객체지향에 대한 깊은 고찰이 함께 수반되어야 한다.
public 메서드에 대해서만 테스트를 진행하는 입장을 취하고, private 메서드의 테스트가 고려되는 상황이라면 설계를 다시 고려하고 고려하라.
'JAVA' 카테고리의 다른 글
[SPRING] 인프런 스프링 기본편 강의 공부기록. 上 (0) | 2022.03.02 |
---|---|
[SPRING] 인프런 스프링 입문 강의 공부기록. 12강 ~ (0) | 2022.02.28 |
[SPRING] 인프런 스프링 입문 강의 공부기록. ~ 11강 (0) | 2022.02.24 |
[JAVA] 참조변수 this, 생성자 this() (0) | 2022.01.13 |
JAVA 타입간의 변환방법 (0) | 2022.01.07 |