본문 바로가기

Clean Code

[PR 코드리뷰] 우아한테크코스 프론트엔드 레벨1 - 로또 미션

 

우아한테크코스 프론트엔드 과정 레벨1 <로또 미션>을 수행하며 토스페이먼츠의 @Jbee 님께 리뷰를 받을 수 있었다. 슬랙 내 소통할 수 있는 전용 채널을 따로 만들어주시고, 항상 미리 공지된 시간에 리뷰를 남겨주셔서 스케줄 조정도 도와주시고, 양질의 참고자료도 틈틈이 전달해주셔서 엄청 꼼꼼하게 케어해주신다는 느낌을 받았다. 감사합니다...🙏👍

 

미션 1단계 피드백

👉 PR 바로가기

1. HTML 태그는 웹표준을 준수해서

<form> 내에서 '입력값 제출'을 위해 <button>을 사용할 때는 type="button"이 아니라 type="submit"을 사용하는 것이 접근성에 맞다. <form>의 submit 이벤트를 활용해보는 쪽을 추천한다. 또한, <input>을 사용할 때는 <label>과 함께 사용하며 <label>이 <input>을 자식으로 포함하거나 for와 name으로 의미론적으로 연결되어야 한다. h1~h6 태그를 wrapping하는 요소는 <div>보다 <section>이 적합하다.

+ 참고자료에 따르면 웹표준을 준수하는 것만으로도 자연스럽게 웹 접근성이 향상된다고 한다!

(웹표준과 <label>, <input> , <div>와<section>)

// <label>이 <input>을 자식으로 포함하는 경우
<form action="member.asp" method="post">
 <fieldset>
  <legend>회원가입 정보</legend>
  <p>
   <label>이름
    <input type="text" id="userName" name="name" value="value" />
   </label>
  </p>
 </fieldset>
</form>
// for와 name으로 의미론적으로 연결하는 경우
<label for="userPw">비밀번호</label>
<input type="password" id="userPw" name="userPw">

2. 함수명이 주는 의미

clearInput이라는 함수의 이름은 <input>의 type, checked, disable 등 모든 속성을 clear할 것 같은 인상을 준다. 하지만 실제로는 <input>의 value만 초기화하는 기능만 담고 있다. 함수명이 주는 의미를 다시 한 번 생각해서 네이밍 해보자.

3. 조건식은 최대한 명시적으로

조건식은 truthy, falsy 값으로도 조건 확인이 가능하지만, 아래와 같이 Boolean 값을 전달하도록 조건식을 작성하여 true, false 를 더 명시적으로 드러낼 수도 있다.

// truthy, falsy 값으로 넘겨주는 방법
if (change)

// Boolean 타입으로 명시적으로 넘겨주는 방법
if (change > 0)

4. 상황에 맞는 메서드로

index를 반환받아서 활용할 것이 아니라면 indexOf 보다 includes가 더 어울린다.

// before
if (array.indexOf(number) === -1)

// after
if (array.includes(number))

 

 

미션 2단계 피드백

👉 PR 바로가기

1. HTML 태그 제대로 사용하기

<button>태그는 type="submit" type="reset"에 대해 살펴보자. React도 중요하지만 HTML도 정말 중요하다. (관련 포스팅)

<!-- before -->
<button type="button" class="restart-button">다시 시작하기</button>

<!-- after -->
<button type="reset" class="reset-button" form="winning-number-form">다시 시작하기</button>

2.  class 안에는 필요한 내용만

isOutOfRange, isDuplicated, hasBlank 함수는 WinningNumberInput class 내에 존재할 필요가 없다. 그리고 validate 이후에 valid한지 확인하는 플래그도 개선해보자.

// before
const checkMessage = this.validateInput(inputValues);
this.setState({ checkMessage });

if (this.checkMessage === WINNING_NUMBER_CHECK_MESSAGE.COMPLETED) {
  this.setState(winningNumber);
}

// after
const { checkMessage, isFulfilled } = validateInput(inputValues);
this.setState({ checkMessage, isFulfilled });

if (!this.isFulfilled) {
  return;
}
this.setState(winningNumber);

3. Object 메서드로 데이터 가공하기

RESULT_TABLE_DISPLAY_KEY는 따로 만들지 않아도 되는 배열이다. Object.keys()메서드로 WINNING_PRIZE의 key를 배열로 만들고 sort해주면 된다. 로또 일치갯수 6이라는 값을 동일한 의미로 다른 곳에서도 사용한다면 상수화해서 가져다 쓰는 것이 좋다. 

export const RESULT_TABLE_DISPLAY_KEY = [3, 4, 5, 5.5, 6]; // 불필요한 배열
export const WINNING_PRIZE = {
  6: {
    PRIZE: 2000000000,
    DESCRIPTION: '6개',
  },
  5.5: {
    PRIZE: 30000000,
    DESCRIPTION: '5개 + 보너스볼',
  },
  ...
}

// 개선 전
RESULT_TABLE_DISPLAY_KEY.map((key) => { ... })

// 개선 후
Object.keys(WINNING_PRIZE)
      .sort((a, b) => a - b)
      .filter((key) => WINNING_PRIZE[key].DESCRIPTION !== undefined)
      .map((key) => { .. })

4. 테스트를 위한 trick은 X

테스트 에러를 통과하기 위해 this.$modalClose?.addEventListener과 같이 옵셔널 체이닝을 사용했는데, 이렇게 테스트를 위해 프로덕션 코드에 트릭을 더하면 안된다. 관련 커멘트

5. uncontrolled 컴포넌트라면

controlled 방식으로 구현할 것이 아니라면 input의 각 값은 submit 이벤트 객체에서 가져오면 된다. 관련 커멘트

6. 기타

lottoTickets.length > 0 은 자체로 boolean이다. 

 

 

미션 3단계 피드백

👉 PR 바로가기

1. 각자의 역할만 알맞게

현재 AppStageManager는 lottoTickets, winningNumber, rateOfReturn 등 앱의 거의 모든 것을 알고 있고, 그 역할도 수행하고 있다. AppStageManager는 stage에 따라 호출해야하는 함수들만 배열로 가지고 있고 다른 것은 아무것도 몰라야 한다. 

// 개선 전
constructor() {
  this.stage = APP_INIT;
  this.lottoTickets = [];
  this.winningNumber = {};
  this.rateOfReturn = 0;
  ...
}

다음 validate 함수에서 return 타입에 일관성이 떨어져 '갑분 change' 느낌이 난다. change를 계산하는건 다른 함수에서 처리하는 것이 맞겠다.

// 개선 전
validateInput() {
    ...
    return {
      isError: true,
      message: PURCHASE_AMOUNT_IS_TOO_LOW,
    };
  }

  const change = purchaseAmount % LOTTO_PRICE;

  return {
    isError: false,
    message: PURCHASE_AMOUNT_HAS_CHANGE(change),
    change,  // *** 문제의 갑자기 분위기 잔돈
  };
}

다음 setState 내에 view를 제어하는 부분은 setState가 할 일이 아니다. autoQuantity와 papers의 변화를 observe해서 각각 처리를 해주어야 할 것이다.

// 개선 전
setStates() {
    ...
    
    this.renderQuantitySummary();
    this.autoQuantity === 0 ? disable(this.$paperAddButton) : enable(this.$paperAddButton);
    this.papers.every((paper) => paper.isFulfilled)
      ? enable(this.$ticketIssueButton)
      : disable(this.$ticketIssueButton);
}

다음 getNthElementRemoved에서 indexOf로 해당하는 Index를 찾는것마저 해주는 것이 좋겠다.

// 개선 전
export const getNthElementRemoved = (array, nth) => {
  return array.slice(0, nth).concat(array.slice(nth + 1));
};
...
const index = this.numbers.indexOf(number);

this.setState({ numbers: getNthElementRemoved(this.numbers, index) });

2. 의존성 주입? 제거?

아직 의존성에 대한 고민이 깊지 않은 것 같고, '의존성'이라는 단어에 대한 sync부터 필요해 보인다. 하위 컴포넌트가 깊어질수록 그 의존성이 App아래로 내려가는 문제에 대한 우려는 AppStateManager에게 관리될 컴포넌트와 아닌 컴포넌트를 분류하는 방식으로 해결할 수 있겠다.

3. tabbable & focusable 하려면 

MDN 문서 나와있듯 키보드 focus를 위해 tabIndex를 하나씩 증가시키는 것은 지양해야 한다. tabIndex를 조작하지 않고 focusable한 element를 사용하는 것이 맞다. document html element를 적절하게 배치함으로써 해결할 수 있다. 

0보다 큰 tabindex 값을 피하세요. 접근성 보조기술 사용자의 페이지 탐색과 조작에 방해될 수 있습니다. 대신, 문서의 요소 순서를 논리적인 순서대로 배치하세요.

문제가 되는 <select>는 기본적으로 tab으로 탐색이 가능하다. focus style을 덮어쓸 때는 정말 잘 덮어씌워졌는지, 기존의 접근성에 위배되지 않는지도 살펴봐야한다. 현재의 <select>에 tabIndex를 제거하면 focus는 잘 되지만 스타일이 제대로 적용되지 않아, focus가 안 된 것처럼 보인다.

.quantity-select:focus {
    outline: black !important; // 주의!
}

4. 이 태그를 아시나요...?

잘 모르는 태그라면 태그의 사용방법에 대해 다시 한 번 검색해보고 사용하는 것이 좋겠다. MDN 문서에 따르면 <summary>는 단순히 요약에 대한 정보를 담는 것이 아니라 <details> 태그의 첫번째 자식으로만 사용할 수 있다. 

// 잘못된 사용법
 <div class="d-flex justify-between items-start">
  <summary class="mt-0 mb-4 text-base"> ...

 

+ 반영예정 constructor에서도 초기값으로 할당하기 때문에 문제가 되는 것...(!)

    this.setState({ autoQuantity: 0, manualQuantity: 0, papers: [], maxIndex: 0 });

 

 

 

 

 

다른 크루의 리뷰 메모 👀

피드백 내용 앞의 번호는 순번이 아니라 PR 번호이다. 지금은 아예 이해가 안되는 내용도 있지만... 서당개 3년이면 풍월을 읊는다 했다!

 

아키텍쳐/패턴

#02 View는 하나의 TicketTemplate, TicketDetailTemplate만 제공하고, 배열을 돌면서 렌더링하는 부분, checked 상태에 따라 다른 것을 렌더링 해야 하는 부분은 Controller에서 하는 것이 좋다.

#03 MVC패턴에서 모델은 getTicket(), putLottoNumbers() 와 같은 컨트롤 동작을 하면 안된다.

#04 로또를 멤버변수로 가지고 있지 않고 로또와 뷰를 컨트롤하지 않는다면 컨트롤러라는 이름보다 핸들러라는 이름이 더 적합하다.

#04 파일에 요소에 클래스를 더해주고 빼주는 등의 메서드가 들어가 있다면 view 보다 util이라는 이름이 더 적합하다.

#05 컴포넌트는 최대한 작게 나누는것이 좋다. 컴포넌트 기반 아키텍쳐는 재사용 가능한 컴포넌트를 만들어 나가는 것이다. 재사용 가능하려면 결국 단일 책임원칙을 지키는 것이 중요하다.

#13 MVC 입문자들은 데이터는 Model, 화면은 View, 그 외 모든 로직들은 Controller에 두려고 한다. 하지만 비즈니스 로직은 컨트롤러 이외의 파일에 있어야 한다. Controller는 이것들의 도움을 받아 모델과 뷰 사이에서 '컨트롤'하는 것이 역할이다. 컨트롤러가 여러 개라면 컨트롤러 외부에서 선언해서 여러 컨트롤러에서 사용할 수 있도록 한다. (컨트롤러끼리는 서로 알지 못한다.)

#15 Manager는 게임 클라이언트 프로그래밍에서 많이 쓰이는 패턴 중 하나이다. 보통 싱글톤 패턴으로 전역에서 접근하여 상태를 갱신하거나 가져오는 형태로 사용한다. (전역 상태, store, event bus의 개념을 같이 갖고 있다.)

#16 로또미션에서는 아직은 규모가 작으니까 '필요한 곳에서만 상태를 생성해 관리하고, 나중에 앱이 커지고 앱의 상태를 변경, 참조하는 코드가 많아지면 분리하여 따로 관리하자'는 접근방식이 좋겠다.

#20 디자인패턴을 하나하나 찾아보기 보다 여러가지 문제 상황을 '직접' 경험하고 해결해 나가며 사이클을 빠르게 경험하는 것이 우선이다. 문제를 겪지 않고 문제를 해결하는 디자인 패턴부터 관심을 갖는다면, 공부를 해도 코드에 적용을 못해서 계속 겉돌고 까먹고 잘못 적용하고 하는 경우가 생길 수 있다. 일단 무언가를 만들어보고 요구사항이 바뀌는 상황에 스스로를 많이 노출하고 '아, 그때 이렇게 코드를 작성하면 안되는 것이었구나' 를 직접 느끼는 시간이 필요하다. 

#20 App 클래스는 전체 프로그램을 구동시키는 역할을 한다. App 이 실제로 하고 있는 일이 역할에 맞는 것인지, 혹시 Controller와 View의 일까지 대신하고 있지는 않은지 확인해보자.

#36 Redux에서 리듀서는 사이드 이펙트가 없고 넣은게 같다면 나오는 것도 같은 '순수 함수'이어야 한다. 따라서 내부에서 generateRandomNumber()와 같은 코드가 존재 하면 안된다. 해당 값이 필요하다면 외부에서 결정된 채로 리듀서 내부로 들어와야 한다. 또한, lottoTicket과 같은 도메인 의존적인 상태는 재사용성을 위해 라이브러리가 되는 Store 를 분리하는게 좋다.

 

 

함수/클래스

#01 ES6이후로는 함수 객체 생성자(Object Contructor) 보다 클래스로 작성하는 편이다.

#01 로또 6장을 구매하는 기능과 로또 1장을 만들어서 반환하는 기능는 별개의 함수로 분리하는 것이 좋다.

#02 HTML 템플릿과 같은 문자열 반복해서 붙여줄 때는 repeat() 메서드를 사용할 수 있다.

#04 중복되지 않는 자료구조에서는 set을 사용하는 것이 좋다.

#04 함수와 변수 모두 선언할 경우에는 변수를 상위에 선언한다.

#04 코드의 일부에서 클래스를 사용했다고해서 전체 코드의 일관성을 걱정할 필요는 없다. 인스턴스화가 필요하다면 클래스로 구현하는 것이 적절하다. Class는 ES6부터 추가된 문법으로, 상속과 캡슐화를 표현하기 좋고 this관리가 편해서 인스턴스화가 필요하다면 일반함수보다

클래스로 구현하는 것이 적절하다.

#06 유효하지 않다면 즉시return으로 함수의 진행을 중단시키는 패턴을 쉴드패턴이라고 한다.

#10 Validator가 static 메서드를 가지는 static 클래스라면 Controller에서 Validator를 인스턴스화할 필요는 없다.

#11 Object.freeze() 메서드로 동결된 객체(frozen object)는 수정이 불가능하다. constants.js 내 객체 기호상수에 적용할 수 있다.

#11 private(#) 프로퍼티는 정의된 클래스 내부에서만 접근이 가능하다.(강제) protected(_) 프로퍼티는 정의된 클래스와 상속받는 클래스에서만  접근해야 한다.(컨벤션) 

#13 this._numbers 는 관례적인 표현일 뿐 런타임에서는 여전히 public 하다. this.#numbers 와 같이 #을 사용해서 외부에서 직접 변경하지는 못하게 한다는 의도를 제대로 표현할 수 있다. 실무에서는 Babel을 필수로 사용하기 때문에 #도 사용할 수 있다.

#21 early return으로 빠져나갈 수 있는 부분 아래로 코드 200 줄이 있다고 가정했을 때, early return을 하게 되면 나머지 코드에서 다시 일일이 검증하지 않아도 되고 코드 파악에도 용이하다.

#22 constructor의 쓰임이 없으면 삭제해주는게 좋다.

#28 객체 생성 시 set할 내용이 있다면 별도의 set함수를 실행하기보다 클래스의 constructor에 해당 내용을 작성하자.

#32 reduce() 메서드를 사용하면 깔끔하게 누적 총합을 구할 수 있다.

const totalProfit = lotto.tickets.reduce((acc, ticket) => acc+ ticket, 0);

#38 특정 메서드를 정상적으로 실행하기 위해서 그 전에 어떤 메서드를 꼭 실행해야만 한다면, 사실은 그 둘은 원래 하나였어야 하는건 아닌가 하는 의심을 해보 것이 좋다. 하나로 하는 것이 무조건 맞는 것은 아니지만 의심을 통해 더 좋은 설계가 나올 수도 있습니다.

#38 문'이 아닌 '식'으로만 써야하는 제약이 있는 상황이 아니라면 조건'문'으로 작성된 코드의 가독성이 더 좋다.

// '식'으로 작성한 경우
!numbers.includes(randomNumber) && numbers.push(randomNumber);

// 조건'문'으로 작성한 경우
if(!numbers.includes(randomNumber)) {
   numbers.push(randomNumber);
}

#47 Object.freeze() 는 얕은 동결이다. 객체 안에 객체가 있다면 유틸함수를 만들어서 깊은 동결을 해줄 수 있다.

const deepFreeze = (obj) => {
	Object.keys(obj).forEach((prop) => {
		if (typeof obj[prop] === 'object' && !Object.isFrozen(obj[prop])) {
			deepFreeze(obj[prop]);
    	}
	});
	return Object.freeze(obj);
};


const thickObj = [ { ... }, { ... }]
deepFreeze(thickObj);

 

 

 

컨벤션/폴더링

#02 LottoController() 내부에 LottoModel 외에 다른 Model도 있을 수 있기 때문에 model 이라는 이름보다는 lottoModel이라는 이름이 좋다.

#05 특정 도메인의 문제를 해결하기 위한 함수는 유틸이 아니다.

#06 예외(exception)라는 키워드는 더 이상 처리 할 수 없는 '에러'가 발생한 상태이거나 throw 로 에러를 낸 상태를 나타낼 때 사용한다. 이 에러를 잡아서(catch) 시스템이 죽지 않도록 처리하는 것이 exception handler의 역할이다.

#08 모든 파일명은 컨벤션이기 때문에 팀원들이나 코드에서 일관성만 유지되면 크게 상관없지만 섞어서 쓰면 문제가 될 수 있다. 일반적으로 snake_case와 camelCase 둘을 섞어쓰는 경우는 별로 없다. 대부분의 자바스크립트 스타일가이드와 lint에서는 camelCase를 사용을 권장한다. PascalCase는 클래스명, 컴포넌트명 등 특수한 경우에 사용된다.

#10 로또 컨트롤러의 이름은 lotto 보다 lottoController가 좋다. lotto 네이밍은 로또객체 하나로 오해할 여지가 있다.

#13 메서드 네이밍은 이 메서드를 호출하는 쪽을 위해 내부 로직을 감추는 식으로 짓는다. 택시손님을 예로 들면, 손님의 관심사는 목적지에 도착하는 것 뿐이고 accelAndTurnHandle 과 같이 구체적인 로직은 택시손님에게 노출하지 않는다. 단일 책임은 한 가지 일이 아니라 한 가지 책임이다.

#16 앱이 크다면, i18next, rosetta와 같은 라이브러리로 앱 내에서 사용되는 string들의 key값을 관리하고 interpolation하는(상수에 인자를 넘기는) 컨벤션을 맞추는 것이 좋다. 로또 미션에서는 CHANGE_MESSAGE(cost) { ... } 와 같이 써도 괜찮겠다.

#17 Lotto 객체를 담는 디렉토리 네이밍은 보통 models 또는 domains라고 해도 좋다. (objects는 데이터 타입 같은 느낌이 있다.)

#17 이벤트 핸들러는 on-* 또는 handle-*과 같은 prefix가 적합하다.

#28 로또번호의 range를 검사하거나 입력값의 범위를 검사하는 로직은 "로또"라는 도메인과 연관되어있는 유틸이 아니라 비즈니스로직이다.

#28 handleWinningNumberSubmit라는 handleWinningNumberInput보다 네이밍이 함수의 역할을 더 잘 드러낸다.

#32 boolean 변수에 count 라는 변수명은 헷갈릴 수 있다. is-*, has-*등을 사용하자.

#38 예전에는 tabWidth: 4, printWidth: 80 이, 요새는 tabWidth:2, printWidth: 120이 더 일반적이다.

 

 

DOM

#02 요소의 스타일을 변경하려고 할 때 element.style.display = "none"처럼 sylte을 수정하는 방법보다 요소에 해당 스타일의 이름을 가진 클래스를 add,remove 하는 방법을 사용하는 것이 유연성이 확보에 좋고 유지보수가 쉬워진다. (참고)

#02 form태그에 submit 이벤트를 사용하면 keyup 이벤트를 별도로 잡지 않아도 된다.

#05 페이지의 유일한 엘리먼트라면 id를, 비슷한 성격의 것들 이라면 data-* 를 사용할 수 있다. class는 스타일을 위한 것이므로 스타일이 바뀌어 html 에서 클래스 이름을 바꾸면 기능이 깨질 수 있음에 유의하자.

#06 클래스로 DOM 셀렉팅을 하면 어플리케이션이 변화에 굉장히 취약해진다. 디자인상 화면의 변화가 요구되어 클래스명이 바뀔 수도 있기 때문에 단순히 디자인이 바뀌어 클래스가 바뀌었는데도 기능이 망가지게 된다. DOM조작이나 addEventListener 에는 가능하면 아이디로 셀렉팅해와서 하거나 복수 개의 셀렉팅이 필요하면 data-* 속성을 활용하는 것이 좋다.

#06 $를 수정해서 $().show() 와 같이 사용할 수 있도록 해보자. $함수가 엘리먼트를 반환하지 말고 엘리먼트는 $ 함수 내부에 두고 이 함수 내부의 엘리먼트에 접근하여 조작하는 show 등의 메서드들을 가진 객체를 반환하는 것이 바로 클로저를 이용한 모듈패턴이다.

#08 $(document.querySelector)는 DOM tree를 탐색하는 비용이 든다. 한번만 사용할 거라면 inline으로 작성해도 되지만, 계속 사용할 DOM reference라면 저장해서 재사용하는 것이좋다.

#12 번호보기 토글 시 루프를 통해 모든 .lotto-number의 CSS 속성을 수정하는 방법 대신 상위 엘리먼트 하나만을 수정하는 방법으로 반복문을 제거할 수 있다. 또, 변경이 필요한 모든 CSS 속성('display: inline', 'flex-direction: column' 등)을 각각 수정하지 않고, 단일 클래스만을 수정하도록 할 수 있다.

#14 이벤트 객체에서 얻을 수 있는 값이라면 querySelector('input')와 같이 DOM에 접근할 필요없이 event.target.elements[name].value와 같이 가져오는 것이 좋다.

#17 당첨번호 입력 input에서 가능한 당첨번호 범위에 따라 validate 해주는 것도 UX 측면에서 좋다.

#17 element를 임시로 생성해두는 것이라면 documentFragment를 사용해서 DOM 조작을 최소화하는 것이 좋다.

#19 innerHtml을 이용한 변경은 XSS attack에 취약하다. 그에 대한 대안으로 creatElement이 있다.

#20 페이지 로드 시 요소가 숨어있어야 하는 경우, HTML/CSS로 기본적으로 숨겨놓고, 보여줘야 할 때 JS로 보여주는 것이 맞다. 최초의 숨기는 것까지 JS 로 할 필요는 없다.

#21 on-* 네이밍은 어떤 이벤트가 일어났을 때 라는 의미이므로, 이벤트핸들러가 아닌 함수 이름은 동사로 시작하는게 좋다.

#47 브라우저의 기본동작을 막고 명시적으로 다른 동작을 하려면 e.preventDefault() 를 작성해서 의도를 드러내는 것이 맞다.

 

미션 로직 / UX

#04 한번 로또를 사고나면 구매버튼을 disable 처리를 하거나 총 갯수가 계속 더해지는 방법도 좋다.

#28 요구사항에는 없지만 키보드 (엔터, 탭, ESC 등)로만 앱 전체를 조작할 수 있도록 개선해보는 것도 좋겠다.

 

테스트

#29 테스트는 주어진 상황에서 '예측가능한' 상태를 검증할 때 적절한 방식이다. 테스트를 '앱을 완벽하게 만들기 위한 도구'가 아니라 '조금더 견고하고 실수를 줄이기 위한 도구'로 생각하는 것이 좋다. (테스트를 모두 통과했더라도 버그는 있을 수 있다. 그런 점에서, 랜덤함수와 같은 무작위성 자체는 예측불가능하기 때문에 테스트하기 어렵다. 따라서, 단위테스트와 E2E테스트를 적절하게 구성하는 것이 좋다. 단위테스트의 예시로는 로또 티켓들을 넣으면 랭킹을 알려주는 함수, 랭킹을 넣으면 당첨금을 알려주는 함수, 당첨금과 투자금을 넣으면 수익률을 알려주는 함수와 같이 가장 작은 단위의 함수 테스트가 있겠다. E2E테스트의 예시로는 무작위성을 제거하고 가상의 투자금, 로또번호, 기대되는 수익률을 만들어 하는 테스트가 있겠다. 이렇게 재현/구현/예측할수 없는 값이나 작업을 미리 만들어 놓는 것을 'Mocking' 이라고합니다. 서버 API가 아직 구현되지않았거나, 브라우저에서 돌아가는 코드지만 테스트는 브라우저에서 하지 못할 때 해당 값들을 Mocking하여 테스트에 사용한다.

 

구조도 작성

#36 http://draw.io/ 구조도 그리는 연습할 때 사용하면 좋다.(무료) 

 

1단계 구조도: #7 #9 #14 #19 #26

2단계 구조도: #28 #33 #36 #42 #47 #51

3단계 구조도: #57

 


그 외 키워드: 의존성주입(#6, #13)
, 의존성제거(#17) 크로스브라우징(#16), i18n(소프트웨어의 국제화, #16), DocumentFragement(#17), Presentational and Container Component(#24, 참고) UML(#28)