드디어!! 한달 간의 프리코스 여정이 시작됐다.
이번엔 디스코드로 커뮤니티까지 생긴다 하니 더욱 더 기대가 됐는데 아니나 다를까 첫날부터 열기가 장난 아니었다. 🔥
특히 "함께 나누기"라는 채널이 활발한데, 여기서 WakaTime이라는 재밌는 익스텐션을 추천받아서 프리코스 기간 동안 써볼 예정이다.
두근두근 첫 미션으로는 이전 기수들에서 진행됐던 숫자 야구 문제가 나왔다. 구현 자체는 어렵지 않았는데 난데없이 테스트 코드에 꽂혀서 판다던가, JS 코드 컨벤션을 지키기 위해 리팩토링하면서 일주일을 꽉꽉 채웠다.
이 미션이 아니었다면 스스로 공부하기 힘들었을 내용이라 값진 시간을 보냈다고 생각한다! 그래서 이번 회고엔 미션을 진행하면서 공부했던 내용을 정리하는 것을 위주로 적어보려고 한다.☺️
초기 세팅에서 ApplicationsTest가 주어지고, 이를 성공하는 것이 프로그래밍 요구사항으로 주어지다 보니 자연스럽게 테스트 코드를 먼저 살펴보게 되었다. 이내 머릿속을 스치는 궁금증..
난수는 말 그대로 랜덤한 수인데 어떻게 결과를 예측해서 테스팅할 수 있지?
이 궁금증이 풀려야 마음 놓고 기능 구현을 할 수 있을 것 같아서 jest 기능부터 공부하기 시작했다.
jest는 테스트하고자 하는 코드의 의존성(모듈)을 실제로 호출하지 않고 테스트해 볼 수 있도록 mock 함수(모의 함수)를 제공한다. 이는 '직접 컨트롤 할 수 없는 것'을 '컨트롤 할 수 있는 것'으로 대체해 테스트 할 수 있다는 것을 의미한다.
jest의 모의 함수는 다음과 같은 기능을 제공한다.
mock 함수는 결국 함수의 모조품을 만드는 것이기 때문에 아래 두 개는 타당한 기능처럼 보인다. 그런데 함수 호출을 Capture 한다는 건 뭘까?
mock 함수로 대체된 함수를 코드에서 호출할 때, jest는 해당 mock 함수의 호출 정보(호출된 횟수, 매개변수 등)을 캡처한다. 이 정보가 있기 때문에 toHaveBeenCalledWith()와 같은 matcher 사용이 가능한 것이다!
이것을 염두에 두고 mockQuestions와 mockRandoms 함수를 살펴보자.

jest.fn()을 사용해 모의 함수의 인스턴스를 만들어 원래 모듈을 오버라이드 해줬다. 이제 테스트 시 코드에서 Console.readLineAsync()를 호출하면 모의 함수가 작동될 것이다..mockImplementation(() => {})을 이용해 모의 함수의 구현부를 변경했다. Console.readLineAsync는 inputs 배열을 하나씩 반환하는 함수가 됐다.
Console.readLineAsync 를 오버라이드 해줬다.[1, 2, 3] 이라면 모의 함수를 첫 번째 호출할 땐 1을, 두 번째 호출할 땐 2를, 마지막엔 3을 반환할 것이다. 위 두개 함수에서는 모두 jest.fn()을 사용해서 모의 함수의 인스턴스를 만들어줬다. 그런데 사실 인스턴스를 만드는 방법은 총 3가지다.
이 세 개는 동작이 조금씩 다르다. 먼저 가장 기본이 되는 jest.fn()은 단일 함수를 모킹을 할 때 사용된다. 반면 mock()은 모듈 전체에 대해 모킹을 할 때 사용된다. 만약 모듈 안에 모킹해야 할 함수나 객체가 많은 경우 mock()을 사용하는 것이 더 효율적이다. 마지막으로 spyOn()은 원래 동작을 유지한 채로 모킹하는 것이 가능하다. (실제로 구현부에 원본 함수를 저장하는 로직이 있다)
spyOn()은 말 그대로 함수에 스파이를 붙여서 캡처된 호출 정보를 탐색하는 역할을 한다. 이번엔 getLogSpy 함수를 보면서 얘기해 보자

getLogSpy는 MissionUtils.Console.print 함수를 관찰하는 스파이😎를 생성해 반환하고 있다. 이렇게 생성된 스파이는 Console.print 함수에 전달된 매개변수(문자열)이 올바른지 감시하는 역할을 할 것이다.
// then
messages.forEach((output) => {
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output));
});
이렇게 테스트가 어떤 조건을 만족해야 통과하는지 알고 구현을 시작하니까 조금 더 꼼꼼한 구현이 가능해졌다. 체험판 TDD를 한 것 같기도..?
솔직히 이거 보고 기뻐서 소리지를 뻔 했다🥹.. 이것이야 말로 내가 찾던 것.
요즘 내 1순위 고민이 바로 클린코드(가독성)에 대한 부분이었는데, 프론트엔드를 독학하다 보니 내가 코드가 여러 측면에서 효율적인 코드인지 마땅히 평가받을 기회가 없어 답답함을 느끼던 중이었다. 그런데 드디어 '괜찮은 코드'에 대한 가이드라인이 생긴 것 같아 참 기뻤다.
이 스타일 가이드 중 유용했던 4가지를 적어 보려고 한다.
하나의 객체에서 여러 속성에 접근할 때는 객체 비구조화를 사용하세요. eslint: prefer-destructuring
객체나 배열을 arr[1] 이나 obj.somthing 이런 식으로 접근하면 코드도 깔끔하지 않고 의미 파악이 어려워진다. 사소하지만 코드가 깔끔해지는 효과를 톡톡히 본 부분이었다.
getHint(playerNum) {
const { strike, ball } = this.getCnt(playerNum); // return {strike: 0, ball: 0}
//...
}
상수 이름을 대문자로 짓는 것은 해당 상수가 (1) 내보내기 될 때, (2) const 타입일 때 (값이 재할당되지 못할 때), (3) 그 상수와 상수가 중첩된 속성이 절대 변하지 않는다는 것을 신뢰할 수 있을 때만 하세요.
상수를 선언할 때 대문자에 스테이크 케이스를 써야 한다는 건 인지하고 있었지만, 내보내기된 오브젝트 안에 있는 속성명은 대문자로 적을 필요가 없다는 걸 처음 알았다.
// bad - unnecessarily uppercases key while adding no semantic value
export const MAPPING = {
KEY: 'value'
};
// good
export const MAPPING = {
key: 'value'
};
첨부되어 있는 ESLINT 문서의 quote 페이지를 보면 일관된 부호를 사용하라고 권장하고 있다. Airbnb의 경우 큰 따옴표가 아닌 작은 따옴표를 사용하라고 되어 있어 prettier 설정에서 작은 따옴표로 옵션을 바꿔주었다. (기본은 큰 따옴표 사용으로 되어 있음)
9.3 메소드가 this를 반환하게 함으로써 메소드 체이닝을 할 수 있습니다.
// bad
Jedi.prototype.jump = function () {
this.jumping = true;
return true;
};
Jedi.prototype.setHeight = function (height) {
this.height = height;
};
const luke = new Jedi();
luke.jump(); // => true
luke.setHeight(20); // => undefined
// good
class Jedi {
jump() {
this.jumping = true;
return this;
}
setHeight(height) {
this.height = height;
return this;
}
}
const luke = new Jedi();
luke.jump()
.setHeight(20);
부끄러운 이야기지만 JS로 코딩하면서 OOP를 거의 까먹고 살았다.. 리액트는 함수형 컴포넌트를 기반으로 하기 때문에 class 문법을 거의 쓸 일이 없기 때문에 스스로 돌아봐도 OOP를 지키며 코딩한 것 같진 않다.🥲

결과적으로 이런 납작한(..) 관계가 만들어졌는데 지금 다시 보니 getInputWithValidator는 유틸리티 함수로 따로 분리되었어야 할 것 같다.
외에도 함수 이름 짓기나 로직 묶기 등 아쉬운 점이 많은데 다른 사람 눈에는 어떻게 보일지 궁금하다.
그런 의미에서 스리슬적 PR 링크 올리기😇(리뷰 반사 환영입니다)
https://github.com/woowacourse-precourse/javascript-baseball-6/pull/83
1주차는 길다면 길고 짧다면 짧은 시간이었다. 프로젝트 이후 번아웃과 성장에 목 말랐던 상태가 합쳐져서인지 몰라도 배움 하나하나가 참 소중하게 느껴졌던 것 같다. 앞으로의 미션도 더욱 기대가 되고 한달 뒤 내가 얼마나 성장해 있을지 궁금하다. 이 글을 읽은 모두 같이 화이팅합쉬다!