Programming/개발 회고록

[리팩터링 회고] 굿 바이 Teave | 2. 기술 부채 청산하기 : 테스트 코드 작성 *작성중*

ggsno 2022. 10. 14. 16:11

  리팩터링의 전제 조건은 리팩터링 후에 정상 동작하는 것이다. 리팩터링 후 정상적으로 동작하는지 확인하기 위해서는 테스트가  필요한데, 테스트 자동화가 되어있지 않아 많이 불안한 상태였다. 자동화가 되어있지 않으면 사람의 눈으로 확인하기 때문에 정확하지 않을 수 있고 확인하는 데에 시간이 많이든다. 만약 여러 코드가 의존하고 있는 부분을 리팩터링 한다면 그 부분을 의존하고 있는 코드들을 모두 테스트해봐야 정확한 테스트가 가능한데, 이는 사람이 하기엔 너무 많은 일이다. 하여, 늦었지만 지금부터라도 테스트 자동화를 위해 테스트 코드를 짜 보고자 한다.

  테스트 자동화 환경 설정 (jest)

더보기
yarn add --dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom @types/jest
//package.json

...
"scripts": {
	...
	"test": "jest"
}
...
"jest":{
    "testEnvironment": "jsdom"
}
//cart.test.tsx
import CartPage from "../pages/cart";
import { render } from "@testing-library/react";

it("cart page render", () => {
    const utils = render(<CartPage />);
    expect(utils.container).toMatchSnapshot();
})

무엇을 테스트해야 하나

인터넷에서 유닛 테스트 예시 코드들을 대충 훑어보고 테스트 코드 짜기를 시작했다. 우선 page폴더의 제일 위에 있는 장바구니 페이지부터 조지기로 했다. 

장바구니 페이지 코드
장바구니 UI

  아직 장바구니에 대한 서버 api가 없어서 테스트용으로 만든 정적 json 파일(productInCart)에서 장바구니 정보를 받아온다. 받아온 장바구니에 담긴 product들을 화면에 브랜드 별로 나눠주기 위해 divideByBrand로 정제한다. 후에 서버 api가 완성되면 BFF에서 이 과정을 수행할 것이다. productPrice는 화면 최하단에 나오는 총 금액을 보여주기 위한 값으로, 모든 product의 price를 합해서 Bill 컴포넌트에 전달한다.

더보기

  글을 적다가 떠오른 건데, 이렇게 정적 파일로 테스트 하지 않고 json서버와 같이 임시 서버를 띄워서 테스트해보는 것도 좋을 것 같다. 다음주에 정적파일들을 대체하는 테스트 코드 작성을 해봐야겠다.

할 일이 늘었다

화면 하단의 Bill 컴포넌트

  Bill 컴포넌트부터 테스트해보기 위해 일단 예제코드에 있는 테스트코드를 갈겨봤다. 예제코드는 이전 코드의 화면을 스냅샷으로 저장해두고 바뀐 코드의 화면과 다른 점을 보여주는 듯 했다.

snapshot

 

  이런 방식으로 모든 컴포넌트들을 테스트하면 되는걸까? 뭔가 간단했지만 모든 컴포넌트에 이런 테스트 코드를 만드는 게 여간 귀찮은 일이 아니었다. 도움을 얻기 위해 현업자 친구에게 물어봤다. 너네 회사는 유닛테스트 어케함? 

초천재 개발자가 내 친구?! 우효~

유닛 테스트는 인터페이스에 대한 테스트를 함
인터페이스에 대한 테스트는 외부에서 주입되는 값에 따른 변화가 올바른지 테스트하는 것
핵심 컴포넌트 위주로 테스트 작성할 것

  조언을 토대로 장바구니 페이지에서 핵심적인 부분만 테스트하고자 이 페이지에서 핵심이 뭘지 고민했다.

  1. 인자로 받은 product들을 모두 렌더링 하는가?
  2. 각 product들 중 선택한 product의 price 합산 값을 올바르게 렌더링 하는가?
  3. 선택된 모든 product들의 총 합산 가격을 올바르게 렌더링 하는가?
  4. 전체 선택/선택해제 시 선택 버튼이 올바르게 렌더링 되는가?
  5. 선택/선택해제 시 합산 가격이 올바르게 변경되는가?
  6. product 삭제 시 삭제된 product가 올바르게 제거 되는가?
  7. product 삭제 시 합산 가격이 올바르게 변경 되는가?

  위의 리스트를 토대로 하나씩 테스트코드를 작성해보자. 장바구니 페이지에서 product는 CartProductsContainer 컴포넌트로 전달되어 렌더링된다. CartProductsContainer는 key가 브랜드 이름이고 value가 해당 브랜드의 product들의 배열로 이루어진 객체를 인자로 받는 컴포넌트다. 이 컨네이너 컴포넌트에서 product들을 map으로 풀어서 각각의 product를 CartProduct 컴포넌트로 전달해 렌더링한다.

CartProductsContainer 컴포넌트

  모두 렌더링 하는지 알아보기 위해 CartProductsContainer 컴포넌트에 테스트용 더미 데이터를 넣고 돌린 코드의 스냅샷과 비교한다.

CartProductsContainer 컴포넌트 테스트 코드

  여기까지 하다가 컴포넌트들의 테스트 코드들이 너무 많아질 것 같아서 페이지 단위로 스냅샷을 찍어보기로 했다.

shop 페이지의 스냅샷 테스트

  페이지 단위의 스냅샷에서는 몇 가지 유의해야할 점들이 있었다.

각 페이지 단위로 스냅샷을 찍어두고 리팩터링을 수행해봤다. 내가 예상했던 스냅샷은 랜더링된 픽셀 기준으로 비교하는 것이었지만 클래스 명의 변경까지 모두 잡아냈다.

styled-component의 변수 명을 바꿨는데 클래스 명이 바뀌어서 테스트에서 실패한 모습