우테코 프리코스 2주 차 미션 안내 메일에서는 2주 차의 목표를 다음과 같이 제시했다.
1주 차에서 학습한 것에 더해 함수를 분리하고, 각 함수별로 테스트를 작성하는 것에 익숙해지는 것을 목표로 하고 있어요. 이번에 테스트를 처음 접하시는 분들은 언어별 테스트 도구를 학습하고 작은 단위의 기능부터 테스트를 작성해보길 바랍니다.
하지만 나는 테스트를 작성하는 것을 포기하고, 함수를 분리하는 것에만 집중해서 구현했었다. 왜냐하면 학교 등으로 여유가 없어서 일단 구현부터 다 하고 테스트를 하자!!라는 생각을 했는데 구현을 끝내고 나니 테스트를 공부하거나 테스트 코드를 짤 시간이 남지 않았던 것이다.
그리고 사실 처음 접하는 개념인 테스트에 대한 두려움도 테스트를 공부하지 않았던 큰 이유가 됐다.
하지만 2주 차 피어 리뷰를 하고 다른 팀원들의 테스트 코드를 볼 수 있어서 보고 배울 수 있었고, 무엇보다 팀원 중 한 명이 블로그에 본인도 jest를 처음 보는데 이러이러한 것을 공부해서 결국 테스트에 성공했다는 말을 자신의 블로그에 써주었기 때문에 처음 해보는 같은 상황인 나도 할 수 있다는 용기가 생긴 것 같다.
그래서 비록 늦었지만 지금이라도 공부하여 우테코 프리코스 3주 차 미션에서는 테스트를 해보려고 한다!
이곳에서 jest로 테스트하는 법을 공부해보고, 직접 테스트 코드를 짜보자.
목차는 다음과 같다.
- jest의 개념
- 간단한 테스트 코드 짜보기
- 2주 차 미션에 쓰인 다른 팀원의 테스트 코드 해석하기
설치 방법은 일단 패스하자. npm install만 하면 바로 jest를 사용 가능하도록 우테코 측에서 코드를 제공하고 있는 것 같다.
테스트를 작성하는 대략적인 패턴은 아래와 같다.
test("테스트 설명", () => {
expect("검증 대상").toXxx("기대 결과");
});
그래서 바로 간단한 테스트 코드를 짜보기로 했다.
각 원소의 제곱을 반환하는 코드를 짜보았다.
test("각 원소의 제곱을 반환", () => {
const input = [1, 2, 3];
const result = input.map((e) => (e * e));
expect(result).toEqual([1, 4, 9]);
});
헐.....바로 됐다. 통과가 됐다. 이렇게 쉬운 건지 정말 정말 몰랐다 너무 무섭게 생겨서 진짜 어려운 거인 줄 알았는데....
아주 간단한 테스트 코드 통과에 성공했으니, 이제 바로 적용해보자!
2주 차 미션에 피어 리뷰 스터디 팀원들은 어떻게 테스트 코드를 짰는지 들여다보았다.
허락을 받고 민재 님이 작성하신 2주 차 테스트 코드를 가져왔다.
// InputCheckerTest.js
// 함수를 선언한 각 파일에서 함수를 가져온다.
const {
checkInputLength,
checkInputIsNumber,
checkInputExcludeCertainNumber,
checkInputDuplicateNumber,
checkInputIsOneOrTwo,
} = require('../src/utils/InputChecker');
// Jest의 describe() 함수를 통해 여러 개의 테스트 함수를 묶었다.
// 테스트 파일에 많은 수의 테스트 함수가 작성되어 있는 경우, 연관된 테스트 함수들끼리 그룹화해놓으면 가독성이 좋아진다.
describe('입력값이 유효한지 확인', () => {
test('입력값이 길이 조건과 맞는지 확인', () => {
// 예외 발생 여부를 테스트해야할 때는 toThrow() 함수를 사용한다.
expect(() => checkInputLength('123', 4)).toThrow();
});
test('입력값이 숫자로만 이루어져 있는지 확인', () => {
expect(() => checkInputIsNumber('123aaa')).toThrow();
});
test('입력값에 특정 숫자가 포함되지 않았는지 확인', () => {
expect(() => checkInputExcludeCertainNumber('123120', 0)).toThrow();
});
test('입력값에 중복된 숫자가 있는지 확인', () => {
expect(() => checkInputDuplicateNumber('12342')).toThrow();
});
test('입력값이 1혹은 2인지 확인', () => {
expect(() => checkInputIsOneOrTwo('3')).toThrow();
});
});
input을 체크하는 테스트인 만큼, 예외 발생 여부를 테스트해야 할 때 사용하는 toThrow() 함수를 사용하신 것 같다.
이 테스트 구문들은 아래와 같이 설명할 수 있을 것 같다.
test('테스트 설명', () => {
expect(() => 함수명(들어오면 안되는 값)).toThrow();
});
// RandomNumberTest.js
// 함수를 선언한 각 파일에서 함수를 가져온다.
const { getRandomNumberExceptList, getUniqueNumbersInRange } = require('../src/utils/RandomNumber');
// .toEqual로 반환 값이 이와 동일한지 비교한다.
describe('컴퓨터 숫자값 테스트', () => {
test('리스트에 있는 값을 제외하고 랜덤한 숫자 받아오기', () => {
const numberList = ['1', '2', '3', '4', '5', '6', '7', '8'];
const result = getRandomNumberExceptList(1, 9, numberList);
expect(result).toEqual('9');
});
// 위와 동일함
test('서로 다른 3자리 숫자 배열 만들기', () => {
const result = getUniqueNumbersInRange(1, 3, 3);
expect(result.sort()).toEqual(['1', '2', '3']);
});
});
test 구문 안에 각 함수가 반환하는 값을 변수에 저장하고, 반환값인 그 변수가 .toEqual() 안의 값과 같은지를 비교하는 것 같다.
BallCountTest에서 처음 보는 jest.spyOn
이라는 것이 나와서 찾아봤다.
jest.spyOn
은 해당 함수의 호출 여부와 어떻게 호출되었는지만을 알아내야 할 때 사용한다고 한다.
찾아보니 toHaveBeenCalledWith는 toBecalledWith와 같다고 하여서 toBecalledWith를 키워드로 찾아보았다.(toHaveBeenCalledWith는 검색 결과가 거의 없어서)
알아본 결과 jest.spyOn
부분과 expect(logSpy).toHaveBeenCalledWith('1볼 1스트라이크');
부분을 해석해보면, MissionUtils.Console.print 함수가 호출되었는지, 호출될 때 '1볼 1스트라이크' 값과 함께 호출되었는지 확인하는 테스트 코드인 것 같다.
// BallCountTest.js
// 함수를 선언한 각 파일에서 함수를 가져온다.
const { countBall, countStrike, printBallCount } = require('../src/utils/BallCount');
const MissionUtils = require('@woowacourse/mission-utils');
describe('볼 카운트 테스트', () => {
test('볼의 수 세기', () => {
const userNumbers = ['1', '2', '3'];
const computerNumbers = ['1', '3', '6'];
const result = countBall(userNumbers, computerNumbers);
expect(result).toEqual(1);
});
test('스트라이크의 수 세기', () => {
const userNumbers = ['1', '2', '3'];
const computerNumbers = ['1', '3', '6'];
const result = countStrike(userNumbers, computerNumbers);
expect(result).toEqual(1);
});
// jest.spyOn이 뭐지?
test('볼 카운트 출력', () => {
const userNumbers = ['1', '2', '3'];
const computerNumbers = ['1', '3', '6'];
const ball = countBall(userNumbers, computerNumbers);
const strike = countStrike(userNumbers, computerNumbers);
const logSpy = jest.spyOn(MissionUtils.Console, 'print');
printBallCount(ball, strike);
expect(logSpy).toHaveBeenCalledWith('1볼 1스트라이크');
});
});
다른 예시도 찾아봤다. 아래와 같은 예시도 있었다. 참고하면 좋을 것 같다.
test('spyOn Test', () => {
// 일반 객체
const calculator = {
add: (a, b) => a + b, // 객체 메소드
};
// calculator.add() 메소드에 스파이를 붙임
const spyFn = jest.spyOn(calculator, 'add');
// 객체 메소드 실행
const result = calculator.add(2, 3);
expect(spyFn).toBeCalledTimes(1); // 몇번 호출? -> 1번
expect(spyFn).toBeCalledWith(2, 3); // 뭘로 호출? -> 인자 2, 3 으로
expect(result).toBe(5); // 리턴값은 5와 같나? -> true
});
테스트 코드를 작성하는 것이 생각보다 어렵지 않아서 굉장히 놀랐다. 저번 주에 처음 보는, 뭔가 어려워보인다는 이유로 공부하지 않았던 것을 반성했다.
그리고 사실 오늘 다른 팀원의 테스트 코드를 보고 2주 차에 작성하지 못한 테스트 코드를 작성해보려고 했는데, 3주 차 미션을 빨리 시작해야될 것 같아서😂 하지 못했다.
3주 차 미션에서 테스트 코드를 상세히 작성하고 3주 차 회고에 자세하게 포스팅 할 것이다.