프론트엔드 테스트코드에 대해 알아보자

Sungho Kim·2023년 1월 29일
9

React

목록 보기
6/7
post-thumbnail

개발을 할때 어러 가지 중요한 요소가 있겠지만, "안전성"이라는 카테고리는 빠질 수 없는 중요한 요소라고 생각합니다. 많은 개발자분들이 테스트 코드가 필요한지 필요하지 않은지에 대한 의견이 분분합니다. 하지만, 스테이지에 따라 다를 뿐, 꼭 필요한 작업이라고 생각합니다.

만약에, 자동차를 사러 갔는데, 알고 봤더니 그 자동차가 안전성 테스트, 바퀴의 품질 테스트, 핸들 조작 테스트가 한 번도 진행되지 않았던 자동차라면, 지금 이 글을 보고 계신 여러분은 그 자동차를 구매하실 건가요?

아마도 99%의 여러분의 대답은 No라고 대답할 거로 생각합니다. 저희 개발자가 만드는 사이트, 앱을 프러덕트라고 하는 이유는 아마도 우리가 만들고 있는 프로그램은 "상품"이라 불리기 때문이죠. 개발자가 제품의 생산자라면, 고객이 이 상품에서 불량(버그)을 발견하기 전에 미리 검수 작업을 하는 것은, 어쩌면 당연한 순서입니다.

이 포스팅을 통해 테스트 코드란 무엇인지, 종류는 어떤 게 있는지, 실제로 도입해본다면 어떤 컴포넌트에 어떤 테스트를 진행해볼지 구체적인 예를 들어서 소개해볼까 합니다!

다만, 프론트엔드 테스트 코드에 대해 생각보다 정보가 많진 않아서 제가 소개한 방법이 최적의 테스트 코드다 라고는 절대로 말할 수 없습니다. 그래도 테스트를 진행해보고 싶었지만 어디서부터 시작해야 할지 모르시는 분들을 위해 함께 공유하면 좋지 않을까 하는 마음으로 작성해봤습니다 ! (개선점을 댓글로 말씀해주시면 적극 반영해 보겠습니다!)

TL;DR

  • 테스트 코드란
  • 테스트 코드의 단점
  • 테스트 코드의 장점
  • 테스트 코드 종류
    • Unit Test
    • E2E Test
  • 본격 적용 해보기
    • Unit Test
      • Button Test
      • Input Test
    • E2E Test
      • Sign Up
  • 느낀점
    • 자신감, 확신, 신용
    • 빠른 피드백
    • 다큐멘테이션

테스트란 무엇일까요?

테스트는 개발자가 기대하는 바와 실제로 만들어진 결과의 차이를 자동적으로 확인해주는 프로세스입니다. 리액트 어플리케이션을 테스트할 때, 우리는 어플리에키션이 어떻게 렌더링 되는지와 응답하는지에 대한 확인을 진행합니다.

테스트코드의 단점

테스트는 많은 비용이 수반됩니다. 개발자에게 시간이란 리소스는 굉장히 소중하기 때문에, 테스트 코드를 작성할 경우, 절대적 코드의 양이 단순 기능 구현에 비해 많습니다. 시간을 많이 들이는 작업인 만큼, 비용이 많은 작업이라고 정의하겠습니다. 비용이 높은 작업이기 때문에, 우리(개발자)가 고민해봐야 하는 것은 무엇을 테스트하고 무엇을 테스트 하지 않을지, 테스트를 통해 달성하고 싶은 목표가 무엇인지를 깊이 고민하고 테스트를 접근해봐야 할 것 같습니다.

테스트 코드의 장점

좋은 테스트를 작성하는 것은 실제로 컴포넌트를 개발하는 것보다 시간이 더 많이 듭니다. 그렇다면 우리는 왜 테스트가 필요할까요? 많은 이유가 있겠지만, 테스팅이 가져다주는 가장 중요한 이점은 3가지로 요약될 수 있습니다.

  • 자신감, 확신, 신용을 보장
    - 좋은 테스트 코드는 개발자에게 확신을 줘서 리팩토링을 할때 무언갈 망치지 않을거라는 확신을 줍니다.
  • 빠른 피드백
    - 좋은 테스트 코드는 빠르게 고쳐야할 코드를 알려줍니다.
  • 다큐멘테이션
    - 좋은 테스트 코드는 테스트 코드만으로도 맥락을 이해할 수 있고, 예제로도 이용이 가능합니다.

테스트에 종류에 대해 알아봅시다.

Unit Test

유닛테스트는 보통 개발자 사이에서 쓰이는 언어입니다. 개발자의 시선에서 테스팅을 바라보며 버튼은 이런 식으로 작동해야 해, 인풋을 여기다 적으면 상태가 이런 식으로 업데이트 될 꺼야 등의 테스트입니다. 따라서 모양이나 색상 등의 보이는 게 중요하다기보단, button tag, input tag, function(){}과 같이, 이미 정의된 특정 요소가 이런 식으로 움직여야한다에 개념입니다

유닛테스트의 특징

  1. 유닛테스트는 굉장히 빠르게 작동합니다. 유닛테스트들은 각각 시스템과는 독립적으로 작동하며, async await 등의 비동기를 사용하지 않다는 점이 특징입니다. 따라서 굉장히 빠르고 적은 리소스로 테스트를 할 수 있습니다.
  2. 유닛테스트는 모듈화가 되어 있습니다. 앞서 설명한 것처럼, 다른 기능들과는 독립적으로 작동해야 하기 때문이죠. 모듈화를 진행하면서 어플리케이션의 아키텍쳐 관점에서 장점을 가져다주는 게 특징입니다. 다른 특징은 모듈화를 진행하면서, 유지보수와 확장성에 장점이 있다는 점입니다.

Functional Test(E2E Test)

Funtional test는 개발자의 시선에서의 테스트라기보단, 유저의 입장에서 UI가 어떤 식으로 움직여야 하나에 대한 테스트입니다. Functional test라는 단어의 생김새로 하여금 개발자를 헷갈리게 만들죠. functional test라는 단어를 보면 unit test의 정의와 비슷할것 같지만, 로딩 시간이나 컴포넌트 렌더링 시간, 반응시간, 서버 로딩 시간 등 UI에 관한 테스트를 functional test라고 합니다.

그래서 functional test의 다른 이름은 "UI testing" 혹은 "E2E testing"이라고도 불립니다. functional testing은 함축적으로 UI에서 요구하는 모든 기능을 충족시키는 테스트이며, Unit test의 반대 개념이라고 보시면 이해하기 편할것 같습니다.

Functional test의 특징

  1. 작동하는데 유닛테스트에 비해 오랜 시간이 걸립니다. 이 테스트를 통해 E2E 테스트를 진행할 수 있고, 시스템의 설계 순서에 따른 작동을 하나하나 테스트를 하게되죠. 정말로 큰 단위의 테스팅은 한시간이 넘는 시간을 소요하기도 합니다.
  2. 유닛들이 하나의 통합된 시스템에서 잘 작동하는지 검증합니다. 완벽한 유닛 테스트 커버리지를 갖고 있다 할 지라도, 유닛테스트를 통합하는 단위의 테스트를 진행해야 합니다. 마치 자동차를 만들 때, 완벽한 바퀴, 핸들, 엔진을 만들었을지라도, 이것을 통합해서 잘 작동하는지 보는 것과 비슷합니다.

스토리북+제스트+테스팅 라이브러리 본격 도입해보기

테스트를 하기 전, 프로젝트가 어떤 모양인지, 어떤 맥락인지 이해를 하고 보시면 좀 더 편하게 보실수 있을꺼 같아서 깃헙 링크와 맥락을 먼저 설명드리겠습니다.

깃헙 링크 - https://github.com/svk5496/storybook-jest-boiler-plate
clone해서 보시는것도 추천드려요! 리드미에 클론 방법 써놨습니다 !
혹시 작동안하면 댓글로 말해주세요~

1. 어떤 컴포넌트를 만들것인가

어느 서비스에나 필요한 회원가입 컴포넌트로 예제를 만들어봤습니다. 적당히 복잡한 컴포넌트를 테스트해 보는 게 적용할 것도, 생각할 것도 많다고 생각했습니다. 페이지는 2개로, 로그인과 회원가입으로 나누어져 있지만, 회원가입 안에서 핸드폰 인증(phone Auth)과 아이디 생성(create account) 두 파트로 진행했고, 페이지 안에서 상태관리를 통해 한 단계 한 단계 진행하는 프로세스로 만들어봤습니다.

2. 왜 스토리북인가

단순히 Jest와 testing library를 이용한 예제가 많았지만, 저는 프론트앤드 개발자로서 UI를 직접 보면서 개발하는 것을 좋아합니다. Storybook은 디자인 시스템을 만드는 데 있어서 최적화된 도구라고 생각하기 때문에, 디자인 시스템을 어느 정도 적용하고 테스트를 진행하고 싶었습니다.
특히 테스트를 진행할 때, 어디까지 유닛 테스트를 진행하고, 어디부터는 E2E를 진행할지에 대해 디자인 시스템이 적용되지 않은 테스트를 기획할때 난감한 상황이 왔었습니다. 디자인 시스템을 이용해서 테스트를 했을때 가장 큰 장점은, atom에서 테스트한 Unit test는 템플릿이나 페이지 단위에서 해당 테스트를 진행하지 않는다는 점입니다.

디자인 시스템에 대해 궁금하신분은 여기 링크를 클릭해주세요

3. 컴포넌트 구조

storybook
ㄴ Atom
  - ButtonText
  - ButtonIcon
  - Input
ㄴ Molecules
  - AuthHeader
ㄴ Templates
  - PhoneAuth
  - CreatAccount
ㄴ Pages
  - Login
  - SignUp
App
Index
Styles
...

4. E2E 테스트는 apollo client와 graphql을 이용해서 진행했습니다.

Unit Test

앞서 언급한 것과 같이, Unit test는 async, await등이 없이, 개발자의 관점에서 컴포넌트를 보지 않아도 이 컴포넌트는 어떤 식으로 작동해야 해 라는 것을 규정하는 테스트입니다.
따라서 데이터를 가져오지 않아도 되는 단계, molecules까지를 unit test 레벨로 규정하고 테스트를 진행했습니다. 대표적인 예제로 atom 중 Text button과 Input 두 가지를 설명하겠습니다.

스토리북에서는 각 스토리가 테스팅 단위를 의미합니다. 제스트의 describe()이랑 비슷한 의미로 하나의 테스트 단위입니다. 제스트에서도 describe안에서 여러개 혹은 단계별 test()를 진행하듯, 스토리북에선 .play를 사용해 각각의 테스트 수트를 만들 수 있습니다.

Button Test

먼저, 버튼을 만들때 뭐를 테스트할지 잠깐 생각해보면,
1. 버튼이 버튼인지 체크를 해야 합니다 (div, span등으로도 버튼을 만들 수 있기 떄문이죠) - tag 확인
2. 버튼 색상이 잘 나왔는지 체크합니다 - css 확인
3. 버튼 안에 label을 전달 했을때, 라벨이 다큐멘트에 존재하는지 체크합니다. - prop 확인
4. onclick을 전달 받았을때, 해당 onClick을 잘 수행할지 체크합니다 - method prop 확인

이외에도 width, height은 받았는지, 테스트를 할 수 있지만, 큰 범위에서 2번 css 확인과 비슷한 작업이기 때문에 다른 테스트는 생략했습니다.

import React from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { within } from "@storybook/testing-library";
import { expect } from "@storybook/jest";
import { ButtonText } from "./ButtonText";
import { lightTheme } from "../../../styles";

-export default {
  title: "DesignSystem/Atoms/ButtonText",
  component: ButtonText,
-  argTypes: {
    backgroundColor: { control: "color" },
  },
} as ComponentMeta<typeof ButtonText>;

-const Template: ComponentStory<typeof ButtonText> = (args) => (
  <ButtonText {...args} />
);

Primary.args = {
  label: "Button",
  variant: "primary",
  fullWidth: false,
  onClick: () => console.log("clicked!"),
};

Primary.play = async ({ canvasElement }) => {
  let canvas = within(canvasElement);
  let primaryButton = await canvas.getByRole("button", { name: /Button/i });
  // 버튼이라는 role과 Button이라는 텍스트를 갖고 있는 버튼을 찾아서
  await expect(primaryButton.innerText).toBe("Button"); // "해당 엘레멘트가 버튼인지 검사를 해라"
  await expect(primaryButton).toHaveStyle(
    `background-color: ${lightTheme.primary}`
  ); 
  // "아까 명시한 버튼이 프라이머리 컬러를 갖고 있는지 검사를 해라"
  
  const consoleSpy = jest.spyOn(console, "log");
  await userEvent.click(canvas.getByTestId("test-button-text"));
  //test-button-text라는 테스트 아이디를 갖고 있는 버튼을 누르고
  expect(consoleSpy).toHaveBeenCalledWith("clicked!");
};
  //눌렀을때 "clicked!"가 콘솔에 출력되었는지 확인해라

이렇게 컴포넌트 별로 스토리 컴포넌트를 따로 제작해서 테스트를 작성하고 저장을 하게 되면,

이렇게 각 단계별 어디에서 오류가 있었는지, 테스트를 통과 하는지 등을 눈으로 볼수 있습니다.

Input Test

인풋에는 어떤 요소들을 테스트 해야할까요? 우선 제가 만든 인풋의 모양부터 보면서 생각해보겠습니다. 제 인풋은 place holder가 최초에 보이다가, 인풋에 텍스트가 입력이되면 플레이스 홀더가 사라지면서 왼쪽 위로 라벨이 올라가게 구현했습니다.

  1. 플레이스 홀더가 보이는지 체크합니다
  2. 최초 인풋 안에 아무것도 없는지 체크합니다.
  3. 인풋값이 들어갔을때 해당 값이 DOM요소에 잘 있는지 체크합니다.
import React from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { Input } from "./Input";
import { userEvent, within } from "@storybook/testing-library";
import { expect } from "@storybook/jest";

export default {
  title: "DesignSystem/Atoms/Input",
  component: Input,
  argTypes: {
    backgroundColor: { control: "color" },
  },
} as ComponentMeta<typeof Input>;

const Template: ComponentStory<typeof Input> = (args) => <Input {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  label: "이메일",
  errorMessage: "에러 메세지",
  fullWidth: false,
  children: (
    <input data-testId="test-email-input" type="text" placeholder="이메일" />
  ),
};

Primary.play = async ({ canvasElement }) => {
  let canvas = within(canvasElement);
  canvas.findByPlaceholderText("이메일");

  const Input = await canvas.findByTestId("test-email-input");
  await expect(Input).toHaveValue("");
  await userEvent.type(
    canvas.getByTestId("test-email-input"),
    "otter0513@naver.com"
  );
  await expect(Input).toHaveValue("otter0513@naver.com");
};

E2E test

마지막 테스트인 통합 테스트, E2E테스트입니다. E2E 테스트는 template과 page단위에서 테스트를 진행합니다. login과 sign up 두개의 페이지가 존재하지만, sign up컴포넌트가 과정이나 검사를 해야할게 더 많기 때문에 Signup으로 진행하겠습니다.

Sign Up

일단 제가 회원가입을 어떤식으로 만들었는지 화면 먼저 보겠습니다!

간단하게 설명하자면,
폼에서 validation을 진행하고, vaildate이 잘 된 경우에 상태값 step이 증가하면서 각 요소가 추가되는 컴포넌트 입니다.

테스트 시나리오를 생각해봅시다.

  1. 이름(렌더링)
  2. 영문, 숫자를 썼을때 오류가 나오는지 (벨리데이션)
  3. 다음 버튼을 눌렀을때 주민등록번호가 나오는지(렌더링)
  4. 주민등록번호, 성별 (벨리데이션)
  5. 핸드폰번호 컴포넌트 (렌더링)
  6. 핸드폰 번호 (벨리데이션)
    번호 형식이 맞는지
    중복번호가 아닌지
  7. 인증번호 모달창이 열리는지 (렌더링)
  8. authNumber (벨리데이션)
  9. 2페이지 (렌더링)
  10. 이메일 플레이스 홀더 (렌더링)
  11. 이메일 (벨리데이션)
    이메일 형식
    이메일 중복
  12. 비밀번호 플레이스 홀더 (렌더링)
  13. 비밀번호 (벨리데이션)
  14. 비밀번호 확인 플레이스 홀더 (렌더링)
  15. 회원가입 버튼 (렌더링)

이중 잘 생각해보면
벨리데이션 - 2, 4, 6(번호형식), 11(이메일 형식), 13 단계는 모두 unit test에서 진행할수 있을꺼 같습니다.

그럼 다시 정리해보면

  1. 이름(렌더링)
  2. 주민등록번호가 나오는지(렌더링)
  3. 핸드폰번호 컴포넌트 (렌더링)
  4. 핸드폰 번호 중복체크 (벨리데이션)
  5. 인증번호 모달창이 열리는지 (렌더링)
  6. authNumber (벨리데이션)
  7. 이메일 플레이스 홀더 (렌더링)
  8. 이메일 중복체크 (벨리데이션)
  9. 비밀번호 플레이스 홀더 (렌더링)
  10. 비밀번호 확인 플레이스 홀더 (렌더링)
  11. 회원가입 버튼 (렌더링)

이렇게만 진행을 해보면 되겠네요.

E2E 테스트의 가장 큰 목정중 하나는 API가 잘 작동 하는지 입니다. 따라서 Mock data들을 미리 정의하는 사전작업이 필요합니다. 제 예제 같은 경우, graphql + apollo를 사용했기 때문에 스토리북에서 지원하는 addon-apollo-client 기능을 활용했습니다.

apollo-client 설치 관련 링크
Apollo-mock-data 활용에 대한 공식문서

Mock data

Primary.parameters = {
  apolloClient: {
    mocks: [
      {
        request: {
          query: VALIDATION_QUERY,
          variables: {
            phone: "01012341234",
          },
        },
        result: {
          data: {
            validationPhone: {
              id: 10,
              phone: "01012341234",
            },
          },
        },
      },
      {
        request: {
          query: ID_VALIDATION_QUERY,
          variables: {
            email: "aa@naver.com",
          },
        },
        result: {
          data: {
            validationEmail: {
              id: 3,
              email: "aa@naver.com",
            },
          },
        },
      },
      {
        request: {
          query: CALL_NAVER_API_MUTATION,
          variables: {
            phone: "01043214321",
          },
        },
        result: {
          data: {
            callNaverSMS: {
              authNumber: "123123",
              error: null,
              ok: true,
            },
          },
        },
      },
      {
        request: {
          query: CREATE_ACCOUNT_MUTATION,
          variables: {
            korName: "홍길동",
            username: "aa@naver.com",
            password: "qwerQWER1@",
            birthYear: "91",
            birthDate: "0513",
            gender: "male",
            phone: "01043214321",
          },
        },
        result: {
          data: {
            createAccount: {
              error: null,
              ok: true,
            },
          },
        },
      },
    ],
  },
};

예제를 보면, 번호, 이메일, 인증번호를 입력할때마다 api를 호출해서 validation을 진행하게 됩니다. 위에 3개는 rest api로 로면 read를 담당하는 api, 밑에 하나는 create을 담당하는 api입니다.

테스트 코드

Primary.play = async ({ canvasElement }) => {
  const canvas = within(canvasElement);
  const nextButton = canvas.getByRole("button", { name: /다음/ });
  // 1.이름 렌더링
  await canvas.findByPlaceholderText("이름");
  userEvent.type(canvas.getByTestId("test-name-input"), "김성호");
  fireEvent.click(nextButton);

  // 2.주민등록번호 렌더링
  await canvas.findByText("주민등록번호 앞 7자리");
  userEvent.type(canvas.getByTestId("test-birth-input"), "910513");
  userEvent.type(canvas.getByTestId("test-gender-input"), "1");
  fireEvent.click(nextButton);

  // 3. 핸드폰 번호 인풋 렌더링
  await canvas.findByPlaceholderText("핸드폰 번호");
  userEvent.click(canvas.getByRole("checkbox")); //체크박스 동의
  userEvent.type(canvas.getByTestId("test-phone-input"), "01012341234");
  expect(canvas.getByDisplayValue("01012341234")).toBeInTheDocument();

  const authButton = await canvas.findByRole("button", {
    name: /인증번호받기/,
  });

  // 4. 핸드폰 번호 중복 체크
  setTimeout(async () => {
    fireEvent.click(authButton);
    canvas.findByText(/해당 핸드폰 번호로 가입된 아이디가 존재합니다/);

    //5. Auth Modal창이 잘 열리는지 체크
    setTimeout(async () => {
      userEvent.clear(canvas.getByTestId("test-phone-input"));
      userEvent.type(canvas.getByTestId("test-phone-input"), "01043214321");
      fireEvent.click(authButton);
      await canvas.findByText(/인증번호/);

      // 6. AuthNumber 벨리데이션
      setTimeout(async () => {
        userEvent.type(canvas.getByTestId("test-auth-input"), "123123");
        const confirmButton = await canvas.findByRole("button", {
          name: /확인/,
        });
        fireEvent.click(confirmButton);

        // 7. 이메일 플레이스 홀더 체크
        await canvas.findByPlaceholderText("이메일");
        userEvent.type(canvas.getByTestId("test-email-input"), "bb@naver.com");
        const nextEmailButton = canvas.getByRole("button", { name: /다음/ });

        // 8. 이메일 중복 체크
        setTimeout(async () => {
          fireEvent.click(nextEmailButton);
          await canvas.findByText(/해당 이메일로 가입된 아이디가 존재합니다/);
          userEvent.clear(canvas.getByTestId("test-email-input"));
          userEvent.type(
            canvas.getByTestId("test-email-input"),
            "aa@naver.com"
          );
          fireEvent.click(nextEmailButton);
          // 9. 비밀번호 플레이스 홀더 체크
          await canvas.findByPlaceholderText("비밀번호");
          userEvent.type(
            canvas.getByTestId("test-password-input"),
            "qwerQWER1@"
          );
          fireEvent.click(nextEmailButton);

          // 10. 비밀번호 확인 플레이스 홀더 체크
          await canvas.findByPlaceholderText("비밀번호 확인");
          userEvent.type(
            canvas.getByTestId("test-password-confirm-input"),
            "qwerQWER1@"
          );
          // 11. 회원가입 렌더링 체크
          const nextRegisterButton = canvas.getByRole("button", {
            name: /회원가입/,
          });
          expect(nextRegisterButton).toBeInTheDocument();
        }, 500);
      }, 500);
    }, 500);
  }, 500);
};

완성

느낀점

테스트 코드의 장점 3가지를 기준으로 말해보자면,

자신감, 확신, 신용을 보장

테스트가 끝났다고 해서 없었던 자신감이 오르거나 하진 않았지만, 확장성과 유지보수 측면에서 봤을 때 확실히 효과가 있을 꺼라 생각이 됩니다. 예를 들어, 버튼에 onClick 메소드를 테스트 했기 때문에, 해당 버튼을 이용해서 새로운 컴포넌트를 만들때, 버튼 내부에는 문제가 없을 거란 확신이 생겼습니다.

빠른 피드백

스토리북 jest와 테스팅 라이브러리를 사용한 결과, 저장할 때마다 에러가 있는지, 에러가 있다면 어디에 어떻게 나는지 즉시 피드백이 나왔습니다. 기존에는 단순히 컴포넌트를 만드는 build에서 끝났다면, 해당 컴포넌트가 데이터를 받으면 이런 식으로 작동을 한다는 것을 눈으로 볼 수 있다는 게 큰 장점이었습니다. 기존에는 회원가입 인풋에 정보를 1, 2, 3, 4 step을 나눠서 직접 테스트를 했어야 했는데, 지금은 한번 만들어놓으면 저장할 때마다 테스트가 되는것도 눈에 띄는 장점이었습니다.

다만, 아직은 익숙하지 않아서 어떤걸 검증해야할지에 대한 리소스와, 검증을 어떻게 할지 생각하는 시간이 꽤 길었습니다. 개인적인 느낌은, 바닐라 자바스크립트를 쓰다가 리액트를 처음 쓸때 같은 버벅거림이였습니다. 조금만 익숙해지면 충분히 커버 가능할꺼란 생각을 했습니다.

다큐멘테이션

스토리북 테스트의 장점은 눈으로 어떤 테스트를 할지 다 보면서 할 수 있다는 점이지만, 기존에 제스트같이 describe이 없었습니다. 이부분은 주석을 달아서 해결하면 되는 부분이라 크게 아쉽지는 않았습니다.

다큐멘테이션의 연장선이 리팩토링이라 생각하는데, 기존에 테스트가 어떻게 작성되어있는지 보는 것 만으로도 이 컴포넌트는 이런 식으로 작동을 하게끔 설계가 되어있구나를 한눈에 볼 수 있었습니다. 아직 테스트 코드를 작성한 컴포넌트를 리팩토링을 해본적은 없지만 리팩토링을 진행할 때 도움이 될 거란 생각이 들었습니다.

참고한 자료

profile
공유하고 나누는걸 좋아하는 개발자

0개의 댓글