[우아한테크코스] 프리코스 3주차 회고록

Jerry·2023년 11월 9일

우아한테크코스

목록 보기
3/3
post-thumbnail

2주 차 공통 피드백


기억에 남는 부분을 적어본다.

처음부터 큰 단위의 테스트를 만들지 않는다


3주 차 목표


1. 클래스(객체)를 분리하는 연습

예전에는 처음부터 클래스를 다 정하고 코드를 작성하려고 했었는데 그러다 보니 구조 설계에 너무 많은 시간이 걸리고 막상 코드를 작성하기 시작한 이후 변경되기 일쑤였다.

기능 목록을 클래스 설계와 구현, 함수(메서드) 설계와 구현과 같이 너무 상세하게 작성하지 않는다. 클래스 이름, 함수(메서드) 시그니처와 반환값은 언제든지 변경될 수 있기 때문이다. 너무 세세한 부분까지 정리하기보다 구현해야 할 기능 목록을 정리하는 데 집중한다.

2주차 공통 피드백 중

처음 시작할 때는 LottoGame, LottoShop, View, Lotto 클래스만 가지고 시작했다. LottoShop에서 Lotto 인스턴스를 생성하기 위해 필요한 번호를 생성해주는 RandomNumbersGenerator 클래스를 만들었고, Lotto 인스턴스들을 담을 객체가 필요하다는 생각으로 LottoPurchaser 클래스가 생기게 되었다.

2. 도메인 로직에 대한 단위 테스트를 작성하는 연습

도메인 로직

도메인 로직에 대해 찾아본 이후 코드가 ‘현실문제에 대한 의사결정을 하고 있는가’를 기준으로 다른 코드와 도메인 로직을 분리했다.

단위 테스트

import Lotto from '../src/Lotto.js';

describe('로또 클래스 테스트', () => {
  describe('includes', () => {
    const lotto = new Lotto([45, 1, 44, 10, 16, 19]);

    test('주어진 숫자가 로또 번호 중에 있으면 true', () => {
      expect(lotto.includes(45)).toBeTruthy();
    });

    test('주어진 숫자가 로또 번호 중에 없으면 false', () => {
      expect(lotto.includes(2)).toBeFalsy();
    });
  });

  describe('로또 번호와 다른 번호의 일치 숫자 개수가 정확한지 확인하는 테스트', () => {
    const lotto = new Lotto([1, 2, 3, 4, 5, 6]);
    const countMatchingWith = (numbers) => {
      return lotto.countMatchingWith(new Lotto(numbers));
    };

    test('아무것도 일치하지 않으면 0이 나온다.', () => {
      expect(countMatchingWith([7, 8, 9, 10, 11, 12])).toBe(0);
    });

    test('1개 일치하면 1이 나온다.', () => {
      expect(countMatchingWith([1, 8, 9, 10, 11, 12])).toBe(1);
    });

    test('2개 일치하면 2이 나온다.', () => {
      expect(countMatchingWith([1, 2, 9, 10, 11, 12])).toBe(2);
    });

    test('3개 일치하면 3이 나온다.', () => {
      expect(countMatchingWith([1, 2, 3, 10, 11, 12])).toBe(3);
    });

    test('4개 일치하면 4이 나온다.', () => {
      expect(countMatchingWith([1, 2, 3, 4, 11, 12])).toBe(4);
    });

    test('5개 일치하면 5이 나온다.', () => {
      expect(countMatchingWith([1, 2, 3, 4, 5, 12])).toBe(5);
    });

    test('6개 일치하면 6이 나온다.', () => {
      expect(countMatchingWith([1, 2, 3, 4, 5, 6])).toBe(6);
    });
  });
});
import WinningResults from '../src/WinningResults.js';

describe('당첨 결과 클래스 테스트', () => {
  describe('총 수익률에 대한 테스트', () => {
    test('구입금액이 8000원이고 로또 당첨 결과가 5등 1개라면 수익률은 62.5%', () => {
      const winningResults = new WinningResults();
      winningResults.saveResultBy(3);
      expect(winningResults.getProfitRate(8000)).toBe(62.5);
    });

    test('구입금액이 55000원이고 로또 당첨 결과가 4등 1개, 5등 1개라면 수익률은 100.0%', () => {
      const winningResults = new WinningResults();
      winningResults.saveResultBy(4);
      winningResults.saveResultBy(3);
      expect(winningResults.getProfitRate(55000)).toBe(100.0);
    });
  });
});

위 처럼 작은 단위 테스트를 먼저 작성하고

import Lotto from '../src/Lotto.js';
import LottoPurchaser from '../src/LottoPurchaser.js';
import WinningLotto from '../src/WinningLotto.js';
import WinningResults from '../src/WinningResults.js';

describe('로또 구매자 클래스 테스트', () => {
  const lottoPurchaser = new LottoPurchaser(new WinningResults());
  const lottos = [
    new Lotto([8, 21, 23, 41, 42, 43]),
    new Lotto([3, 5, 11, 16, 32, 38]),
    new Lotto([7, 11, 16, 35, 36, 44]),
    new Lotto([1, 8, 11, 31, 41, 42]),
    new Lotto([13, 14, 16, 38, 42, 45]),
    new Lotto([7, 11, 30, 40, 42, 43]),
    new Lotto([2, 13, 22, 32, 38, 45]),
    new Lotto([1, 3, 5, 14, 22, 45]),
  ];
  const winningLotto = new WinningLotto([1, 2, 3, 4, 5, 6], 7);

  lottoPurchaser.purchase(lottos);
  lottoPurchaser.check(winningLotto);

  test('getLottoCount', () => {
    expect(lottoPurchaser.getLottoCount()).toBe(8);
  });

  test('getLottos', () => {
    const answer = [
      [8, 21, 23, 41, 42, 43],
      [3, 5, 11, 16, 32, 38],
      [7, 11, 16, 35, 36, 44],
      [1, 8, 11, 31, 41, 42],
      [13, 14, 16, 38, 42, 45],
      [7, 11, 30, 40, 42, 43],
      [2, 13, 22, 32, 38, 45],
      [1, 3, 5, 14, 22, 45],
    ];

    expect(lottoPurchaser.getLottos()).toStrictEqual(answer);
  });

  test('getWinningResults', () => {
    const answer = [
      [1, 0],
      [2, 0],
      [3, 0],
      [4, 0],
      [5, 1],
    ];

    expect(lottoPurchaser.getWinningResults()).toStrictEqual(answer);
  });

  test('getProfitRate', () => {
    expect(lottoPurchaser.getProfitRate()).toBe(62.5);
  });
});

마지막에 전체를 아우르는 테스트를 작성했다.

3주 차 미션을 하면서 시도해 본


메시지 템플릿

const WINNING_RESULTS_BY = {
  1: (count) => `6개 일치 (2,000,000,000원) - ${count}개\n`,
  2: (count) => `5개 일치, 보너스 볼 일치 (30,000,000원) - ${count}개\n`,
  3: (count) => `5개 일치 (1,500,000원) - ${count}개\n`,
  4: (count) => `4개 일치 (50,000원) - ${count}개\n`,
  5: (count) => `3개 일치 (5,000원) - ${count}개\n`,
};
Object.freeze(WINNING_RESULTS_BY);

const TEMPLATE = {
  winnigResultsBy: WINNING_RESULTS_BY,
  lottoCount: (lottoCount) => `\n${lottoCount}개를 구매했습니다.`,
  sortedLotto: (sortedLotto) => `[${sortedLotto}]\n`,
  profitRate: (profitRate) => `총 수익률은 ${profitRate}%입니다.`,
};
Object.freeze(TEMPLATE);

export { QUERY, ERROR, WINNING_STATISTICS, TEMPLATE };

에러 메시지 출력 후 다시 입력 값 받기

async #purchaseLottos() {
  try {
    LottoShop.sellTo(
      this.#lottoPurchaser,
      await InputView.askPurchaseAmount(),
     );
  } catch (error) {
    OutputView.print(error.message);
    await this.#purchaseLottos();
  }
}

async #saveWinningLotto() {
  try {
    this.#winningLotto = new WinningLotto(
      await InputView.askWinningNumbers(),
      await InputView.askBonusNumber(),
    );
  } catch (error) {
    OutputView.print(error.message);
    await this.#saveWinningLotto();
  }
}

validate()

이전 주차까지는 입력 값을 검증하는 코드를 전부 View 파일에 넣었다.
validate() 메서드가 Lotto 객체에 들어있어서 입력값을 검증하는 코드가 어디에 위치하는 게 맞는가에 대해서 생각해 보는 시간을 가졌다.
Lotto 객체의 속성에 따른 검증은 Lotto 객체에서 이뤄지게 했다. 예를 들어 이미 위 예시에 있듯이 lotto는 6개의 숫자로 이뤄지는 것과 같은 그런 것

혹시 피드백 해주실 사항이 있다면 꼭 남겨주세요 :)
아주 사소한 것이라 해도 상관 없습니다!

PR 링크
https://github.com/woowacourse-precourse/javascript-lotto-6/pull/538

profile
I'm jerry

0개의 댓글