[TDD] 역할분리의 적신호. Private method 테스트

pengooseDev·2023년 7월 23일
3
post-thumbnail

private method를 테스트 해야하는 상황에 직면했다면, 객체의 역할분리를 다시 고민해보라.

결론 : 해당 로직을 객체로 분리하고 매개변수로 DI하는 방법을 고려해보자.


예시 코드

import { MESSAGE, VALIDATOR } from '../constants';
import { splitCarNameToArray, Validator } from '../utils';

export class GameController {
  #model;
  #view;

  constructor(model, view) {
    this.#model = model;
    this.#view = view;
  }

  play() {
    this.#readCarName();
  }

  #readCarName() {
    this.#view.readCarName(this.#validateCarName);
  }

  #validateCarName(userInput) {
    const carNamesArray = splitCarNameToArray(userInput);
    carNamesArray.forEach((carName) => {
      const isValid = isValidCarNameLength(carName);

      if (!isValid) throw new Error(MESSAGE.ERROR.INVALID_LENGTH(MAX_CAR_LENGTH));
    });

    this.#createCarByArray(carNamesArray);
  }

  #createCarByArray(carNamesArray) {
    this.#model.createCarByArray(carNamesArray);
    this.#playRaceGame(PLAY_ROUND);
  }

  #playRaceGame(playRound) {
    // playRound 횟수만큼 게임을 진행한다.
  }
}

게임이 시작되면, 유저의 입력을 받고, 정해진 게임 횟수만큼 게임을 진행한다.

그렇다면 다음과 같은 테스트 코드를 작성해야한다.

describe('자동차 경주', () => {
	test('입력된 횟수만큼 게임이 진행된다.', () => {
		/* 어라..?? 그런데 생각해보니 #playRaceGame에 어떻게 접근해야하지..?
         TC 짜기가 너무 어려운데..?
        */
	});
})

여기서 문제가 드러난다.

테스트 코드를 작성하려고 마음을 먹은 순간... 캡슐화 된 private method의 테스트코드를 작성할 방법이 도저히 생각나지 않는다는 것이다.


엥? jest.fn()으로 mocking하세요..

물론, TC를 짜본 사람이라면 jest.fn()을 사용해 private method를 mocking하거나 spying하는 것으로 확인하면 되지 않느냐?라고 할 것이다.

맞다. jest.fn()을 사용한다면 TC를 편하게 작성할 수 있다.

??? : 그럼 jest.fn()을 쓰면 되지 왜 이렇게 징징대느냐..?


과제 요구사항

??? : 응 쓰지마~

마음을 가다듬고 다시 생각을 해보자...

프리코스의 레포를 까보더라도, jest.fn()을 사용해 TC를 작성한 것을 알 수 있다.
그런데 쓰지 말라고 할까? 항상 그 이유가 중요하다.

문제를 해결하기 위해, 이런 저런 해결책을 고민해보았다.
6시간 정도가 지난 뒤, 결론이 나온 순간 소름이 돋아버렸다. 그 이유는...


우아한 도메인 분리

현재 나의 코드의 문제점 다음과 같다.

1. 비동기적으로 처리되는 user의 Input2. 레이싱 게임이 하나의 컨트롤러에서 관리되고 있다.

전자의 문제점은 1번. 후자의 문제점을 2번이라고 부르겠다.

import { MESSAGE, VALIDATOR } from '../constants';
import { splitCarNameToArray, Validator } from '../utils';

export class GameController {
  #model;
  #view;

  constructor(model, view) {
    this.#model = model;
    this.#view = view;
  }

  play() {
    this.#readCarName();
  }

  #readCarName() {
    this.#view.readCarName(this.#validateCarName);
  }

  #validateCarName(userInput) {
    const carNamesArray = splitCarNameToArray(userInput);
    carNamesArray.forEach((carName) => {
      const isValid = isValidCarNameLength(carName);

      if (!isValid) throw new Error(MESSAGE.ERROR.INVALID_LENGTH(MAX_CAR_LENGTH));
    });

    this.#createCarByArray(carNamesArray);
  }

  #createCarByArray(carNamesArray) {
    this.#model.createCarByArray(carNamesArray);
    this.#playRaceGame(PLAY_ROUND);
  }
  
  /*
  위쪽이 Model과 View를 잇는 ServiceLayer의 역할
  아래쪽이 말 그대로 "경주 게임"을 진행하는 역할
  */

  #playRaceGame(playRound) {
    // playRound 횟수만큼 게임을 진행한다.
  }
}

위에 적힌대로 현재 Controller는 두 가지 일을 하려고 하고 있다.
1. View와 Model을 관리하는 Service Layer의 역할. (1번)
2. 레이싱 게임을 관리하는 역할. (2번)

분리해야하는 두 개의 도메인이 하나의 객체 내에 존재해, 강한 의존성이 발생하였기 때문에, jest.fn()을 사용하지 않는다면 TestCode를 작성할 수 없어져버렸다는 뜻이다.

여기서 배운 것은 다음과 같다.

테스트 코드에서 jest.fn()을 사용해야 한다는 것은, 하나의 도메인이 너무 많은 일을 하고 있는지 의심해야한다는 뜻이며, 현재 구현된 객체를 더 작은 도메인(관심사)별로 더 잘게 쪼갤 수 있는지 고민해보야 한다는 것을 의미한다.

물론, 위의 것 말고도 많은 해답 존재할 것이다. 하지만, 필자가 6시간 고민하고 내린 결론은 이것이고, 고민하던 모든 것이 해결된다.


우아한 교육 철학

우아한 테크코스 프리코스나 NextStep의 학습과정은 참 대단하다.
간단한 요구사항 한 줄에 정말 많은 뜻과 철학이 담겨있다.

이 요구사항이 간단해 보이는가?

  • 처음에 이 요구사항을 보았을 때, 아무 생각이 들지 않았고,
  • 3일 동안 도메인을 분리하고 캡슐화와 객체의 역할에 집중을 하다보니, 이 문제는 나에게 너무나 어려운 요구사항으로 다가왔고
  • 이 문제를 끝까지 물고 늘어져 해결한 순간, 명확히 "알을 깨고 나왔다"라는 확신이 들며 여태 고민하고 어려워했던 부분들의 해답이 한 번에 보이기 시작했다. 객체는 객체답게 써야한다는 것이다.

우아한 테크코스나 NextStep의 과제는 제대로 고민하고 머리를 짜냈다면.
결국, 같은 고민점에 도달하게 된다. (모각코를 함께 진행하던, 경민님도 같은 고민을 하고 계셨다.)

우아한 형제들이 출제하는 문제들의 해결책은 간단한 코드 한 줄이기도 하고, 간단한 설정이기도 하다. 하지만, 이러한 문제를 해결하는 "코드 한 줄"을 작성하기까지의 고민은, 개발자로서 정말 많은 것을 깨우치게 하며, 개발자 길을 걸어나아가는 기나긴 모든 순간. 나의 곁에서 올바른 방향성을 제시해주는 든든한 나침반의 역할을 한다.

다시 한 번, 우아한 형제들과 NextStep, 포비, 준, 코치님들과 함께 성장을 해 나아가는 크루들께 감사의 말씀을 남긴다.

이 부분을 고민하고 해결하느라 6시간 걸렸다. 하지만, 이 지식은 평생 없어지지 않을 것이라는 것을 이미 프리코스에서 느꼈기 때문에, 정말 이렇게까지 기쁠수가 없다 호호호 😚

이제 다시 전진!

2개의 댓글

comment-user-thumbnail
2023년 7월 23일

좋은 글 감사합니다.

1개의 답글