강신규

[우테코 프리코스/7기] 프리코스 3주 차 회고 본문

우아한테크코스

[우테코 프리코스/7기] 프리코스 3주 차 회고

kangnew 2024. 11. 12. 16:34

 

최종코드:

https://github.com/singyuKang/javascript-lotto-7/tree/singyuKang

 

GitHub - singyuKang/javascript-lotto-7

Contribute to singyuKang/javascript-lotto-7 development by creating an account on GitHub.

github.com

 

 

목표설정

[설계] README.md 작성

 

[개발] 메서드의 길이가 15라인 넘어가지 않도록. 한 메서드는 하나의 기능을 수행하도록 개발

 

[테스트] TDD 및 Unit Test 확인

 

README.md를 통해 전체적인 흐름을 설계하였습니다. 

# javascript-lotto-precourse

## ✏️ 과제 진행 요구 사항 - 구현할 기능 목록

## 입력 함수 구현

- [x] 사용자의 입력을 받는 함수 구현 '구입금액을 입력해 주세요.'를 출력하고 숫자를 입력 받습니다.
- [x] '당첨 번호를 입력해 주세요.'를 출력하고 쉼표를 기준으로 당첨 번호를 입력합니다.
- [x] '보너스 번호를 입력해 주세요'를 출력하고 보너스 번호를 입력합니다.

## 입력 값 처리

- [x] 1,000원 단위로 입력 받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리.
- [x] 당첨 번호 입력시 쉼표기준으로 구분했을때 숫자가 아니면 예외 처리.
- [x] 보너스 번호가 숫자가 아니면은 예외 처리 합니다.

## 로또 생성기 구현

- [x] 구입 금액에 맞춰 로또 번호를 생성합니다.

## 로또 당첨 판별 구현

- [x] 구매한 로또를 확인하여 당첨 내역을 판단합니다.

## 실행 결과 출력

- [x] 금액에 맞춘 구매한 로또를 출력합니다.
- [x] 당첨 통계를 출력해줍니다.

## 기능별 테스트 케이스 추가

- [x] LottoController의 기능 테스트 진행
- [x] LottoTest의 기능 테스트 진행

 


유효성 검사

사용자 입력을 도메인으로 변환할 때의 유효성 검사 위치에 대해 고민한 결과, 입력 단계에서 1차 검증을 하고, 도메인 내부에서 다시 검증하는 방식을 사용하였습니다. 이러한 이중 검증 방식은 장점으로 입력 데이터의 신뢰성을 높여 예외 상황을 줄이고, 도메인의 안정성을 강화합니다. 하지만 단점으로는 코드 중복과 개발 시간이 늘어나 프로젝트의 복잡성이 증가할 수 있습니다. (유효성 검사 위치는 프로젝트의 요구사항과 구조에 따라 다를 수 있음)

const InputValidation = {
  validateAmount(amount) {
    if (amount % LOTTO_EACH_AMOUNT !== 0) {
      throw new Error(ERROR_MESSAGE.LOTTO_MONEY_INPUT_ERORR);
    }

    if (isNaN(amount) || amount === "") {
      throw new Error(ERROR_MESSAGE.LOTTO_MONEY_INPUT_STRING_ERORR);
    }
  }
};

 

class Lotto {
  #numbers;

  constructor(numbers) {
    this.#validate(numbers);
    this.#numbers = numbers;
  }

  #validate(numbers) {
    if (numbers.length !== LOTTO_COUNT_MAX) {
      throw new Error(ERROR_MESSAGE.WINNING_LOTTO_LENGTH_ERROR);
    }
    numbers.forEach((value) => {
      if (isNaN(value) || value === "") {
        throw new Error(ERROR_MESSAGE.WINNING_LOTTO_NUMBER_ERROR);
      }
      if (
        Number(value) > LOTTO_MAX_NUMBER ||
        Number(value) < LOTTO_MIN_NUMBER
      ) {
        throw new Error(ERROR_MESSAGE.WINNING_LOTTO_MAX_MIN_ERROR);
      }
    });
    if (hasDuplicatesArr(numbers)) {
      throw new Error(ERROR_MESSAGE.WINNING_LOTTO_NUMBER_DUPLICATE_ERROR);
    }
  }

  getNumbers() {
    return this.#numbers;
  }
}

 

 


단위테스트

 

이번 미션에서 처음 단위 테스트를 사용해 보았습니다.

TDD의 기본원칙에 충실하려고 노력하였습니다.

 

1. 명확한 실패 조건과 예외 처리: 유효하지 않은 입력에 대해 예외가 발생하는지 확인하는 테스트가 포함되어 있습니다. 예를 들어, 번호가 6개 이상이거나 중복되는 경우, 범위를 벗어나는 경우에 예외를 발생. 로또 클래스의 안정성을 검증할 수 있습니다.

 

2. 정상적인 로또 생성 확인: 올바른 입력([1, 2, 3, 4, 5, 6])이 주어졌을 때, getNumbers() 메서드를 통해 숫자가 정확히 6개로 구성되었는지를 확인합니다

 

3. 테스트 케이스의 독립성: 각 테스트가 독립적으로 실행되며, 특정 기능을 개별적으로 검증합니다. 이로 인해 특정 테스트가 실패해도 다른 테스트에 영향을 주지 않아, 원인 파악이 용이합니다.

 

describe("로또 클래스 테스트", () => {
  test("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.", () => {
    expect(() => {
      new Lotto([1, 2, 3, 4, 5, 6, 7]);
    }).toThrow("[ERROR]");
  });

  test("숫자 6개를 가진 로또를 생성한다.", () => {
    const lotto = new Lotto([1, 2, 3, 4, 5, 6]);
    expect(lotto.getNumbers()).toHaveLength(6);
  });

  test("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.", () => {
    expect(() => {
      new Lotto([1, 2, 3, 4, 5, 5]);
    }).toThrow("[ERROR]");
  });

  test("로또 번호는 1~45 사이에 있어야 하며 그외에는 예외처리한다", () => {
    expect(() => {
      new Lotto([1, 2, 3, 4, 5, 46]);
    }).toThrow("[ERROR]");
  });
});

describe("로또 컨트롤러 기능 테스트", () => {
  const LOTTO_EACH_AMOUNT = 1000;

  let lottoController;

  beforeEach(() => {
    lottoController = new LottoController(5000);
  });

  test("로또 구매 갯수와 생성된 로또 배열 확인", () => {
    expect(lottoController.getLottoTotalNumber()).toBe(5);
    expect(lottoController.getAllLottos().length).toBe(5);
  });

  test("로또 getAllLottos, calculateWinningLottos 계산확인", () => {
    const winningLotto = {
      getNumbers: () => [1, 2, 3, 4, 5, 6],
    };
    const bonusNumber = 45;

    lottoController.getAllLottos()[0] = {
      getNumbers: () => [1, 2, 3, 4, 5, 6],
    };
    lottoController.getAllLottos()[1] = {
      getNumbers: () => [1, 2, 3, 4, 5, 45],
    };
    lottoController.getAllLottos()[2] = {
      getNumbers: () => [1, 2, 3, 4, 5, 7],
    };
    lottoController.getAllLottos()[3] = {
      getNumbers: () => [1, 2, 3, 4, 8, 9],
    };
    lottoController.getAllLottos()[4] = {
      getNumbers: () => [1, 2, 3, 10, 11, 12],
    };

    lottoController.calculateWinningLottos(winningLotto, bonusNumber);

    expect(lottoController.getWinningPrizeStatistics()).toEqual([
      1, 1, 1, 1, 1,
    ]);
  });
  test("로또 ProfitRate 계산결과 테스트", () => {
    lottoController.calculateWinningLottos = jest.fn();
    lottoController.getWinningPrizeStatistics = jest.fn(() => [1, 1, 1, 1, 1]);

    const profitRate = lottoController.getProfitRate();
    const totalProfit = lottoController.getTotalProfit();
    const expectedProfitRate = (
      (totalProfit / (5 * LOTTO_EACH_AMOUNT)) *
      100
    ).toFixed(1);

    expect(profitRate).toBe(expectedProfitRate);
  });
});

 

 

참고)

Using Matchers

Testing Asynchronous Code

Jest 파라미터화 테스트하기: test.each(), describe.each()

 


느낀점

단위 테스트를 통해 내가 만든 클래스가 요구사항에 맞게 동작하는지 확인할 수 있고, 예상치 못한 상황을 시뮬레이션하여 오류를 미리 발견할 수 있습니다. 이는 프론트엔드 개발자로서 에러 처리 능력을 높이는 데 큰 도움이 됩니다.