TIL[36일차]배포 흐름

남예지·2022년 12월 19일
0

TIL

목록 보기
28/47
post-thumbnail

오늘은 배포 이론이다.
배포 전에 봐야하는 건 테스트다. 이제까지 해왔던 기능들이 잘 작동되는지 볼 것이다.
이 과정을 통해 테스트 코드를 만들어서 테스트를 자동화 하는 방법을 알아보자.

굳이 컴퓨터에 시켜야 하나? 내가 클릭하면 되는데? 아니 만약 버튼이 500개라면?
컴퓨터에 시켜 자동화를 시켜보자 .

업데이트 배포와 코드 이해

테스트 코드를 만들어야 하는 이유!
그러지 않을 경우 생기는 문제점

버튼을 클릭하면 API요청이 나가야함. 이런거 저런거 다 체크를 해가면서 버그를 하나하나 검증해서 잘 만들었다. 그리고 배포를 해서 잘 운영하고 있었다. 사업이 잘 되어 결제기능 등 더 업그레이드 된 기능을 추가 하고, 업데이트를 하려했는데 업데이트하면서 원래 되던 기능이 안되는 에러를 보게 된다. 왜 그런걸까?
업데이트 배포한 코드들이 이전에 배포한 기능에 연관된 코드를 가지고있어 에러를 보게되는 것이다.

그러니 업데이트 후 업데이트 전, 후 모두 다시 테스트를 해야한다는 것입니다. 이런 번거로운 과정을 해결하기 위해 우리는 버튼을 대신 눌러주는 테스트코드가 있어야한다.
전에 배운 허스키를 사용해 테스트가 안맞는다면 업로드가 안되게 할 수 있다.

  • 외주라면 테스트코드를 만들지 않을 수도 있다. 하지만 자사 프로젝트는 테스트코드가 필수니 기능과 테스트코드를 같이 만들어야 한다.

기능과 기능에 대한 테스트 코드는 세트라고 생각하자!


다양한 테스트 방법

ETE테스트는 유저 시나리오 테스트 라고도 한다.

환불 테스트의 시나리오는 단순히 환불이 되었는지만 보는게 아니고 로그인, 구매여부, 환불기간, 환불여부, 포인트 반환, 환불 리스트까지 테스트해야한다.
이렇게 기능에 시나리오를 만들어서 테스트한다.


jest로 테스트코드 만들기

우리는 위 3가지 방법 중 1번 방법인 Jest를 사용하여 개별 단위테스코드를 만들어보자

  1. 파일 구조
    파일은 2개를 만들어주는데 index.ts와 index.test.ts 혹은 index.spec.ts를 만든다.
    하나는 일반 기능 파일이고 다른 하나는 이 파일을 테스트하는 테스트 코드 파일이다.

jest는 리엑트, 앵귤러, 뷰 등에 범용적으로 사용된다.
리엑트 독스에 가면 테스팅에 jest 설명이 있다.
next 독스에도 테스트 부분에는 cypress도 있고 jest도 있다.
필요에 따라 선택해서 사용하면 된다.
리엑트에서 제공하는 jest와 테스트 라이브러리를 같이 사용해야한다.

  1. jest 설치하기

    yarn add --dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom

  2. jest.config.js 파일을 만든 후 아래 내용을 붙여넣어준다.

// jest.config.js
const nextJest = require('next/jest')

const createJestConfig = nextJest({
  // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
  dir: './',
})

// Add any custom config to be passed to Jest
/** @type {import('jest').Config} */
const customJestConfig = {
  // Add more setup options before each test is run
  // setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
  moduleDirectories: ['node_modules', '<rootDir>/'],
  testEnvironment: 'jest-environment-jsdom',
}

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig)
  1. package.json에 가서 script에 "test": "jest"을 추가해준다.

    "test": "jest"

여기서 문제가 발생했다.
만약 Cannot find module 'react-dom/client' from 'node_modules/@testing-library/react/dist/pure.js' 이라는 알림을 봤다면 package.json에 "@testing-library/react": "^13.4.0", 의 버전의 문제일 수 있다.
버전을 12.1.2 로 변경후 yqrn install하고 다시 확인하면 잘 되는 걸 볼 수 있다.


이제 실습을 해보자.

<실습 1> 간단한 함수를 만들고 테스트 파일에 테스트

// index.ts
export const add = (a: number, b: number) => {
  return a + b;
};


//  indes.test.ts
import { add } from "../../pages/34-01-jest";

it("더하기 잘 되는지 테스트 해보기", () => {
  const result = add(3, 5);
  expect(result).toBe(8);
});

결과가 잘 나오는걸 알 수 있다.

만약 테스트가 여러개일때는

// describe("나만의 테스트 그룹만들기", () => {
// if("내가 하고싶은 테스트 1", () => {})

// if("내가 하고싶은 테스트 2", () => {})

// if("내가 하고싶은 테스트 3", () => {})
// })


그런데 next.js를 사용하면 폴더 구조가 달라진다.

파일읠 최 상단에 test 폴더를 만들고 그 안에 테스트한 폴더의 이름과 동일한 폴더를 만든 후 그 안에 테스트 파일을 넣는다. (아래 캡처화면 참조)


TDD : 테스트를 먼저 만드는 방식

회사에 따라 다르지만 기능을 만들고 테스트를 만드는 회사도 있고, 테스트를 먼저 만들고 거기에 맞춰 기능을 만드는 회사도 있다. 테스트를 먼저 만드는 방식을 TDD방식이라고 한다.

우리는 기능을 먼저 만들고 테스트 해보자.

<실습 2> 내가 원하는대로 그려지는지 테스트하기 (screen.getByText)

// index.tsx
export default function JestUnitTest() {
  return (
    <>
      <div>철수는 13살 입니다.</div>
      철수의 취미 입력하기:
      <input type="text" />
      <button>철수랑 놀러가기</button>
    </>
  );
}


//index.test.tsx
// render로 그리고 screen으로 검즘
import { render, screen } from "@testing-library/react";
import JestUnitTest from "../../pages/34-02-jest-unit-test";
// 돔에 그리는게 아닌 제스트 돔에 그려야하기 때문에 import 해줌
import "@testing-library/jest-dom";

it("내가 원하는대로 그려지는지 테스트하기", () => {
  render(<JestUnitTest />);

  const myText = screen.getByText("철수는 13살 입니다.");
  expect(myText).toBeInTheDocument();

  const myText2 = screen.getByText("철수의 취미 입력하기:");
  expect(myText2).toBeInTheDocument();

  const myText3 = screen.getByText("철수랑 놀러가기");
  expect(myText3).toBeInTheDocument();
});

테스트에 잘 통과되었다.

render는 그릴 페이지를 가져온다.
screen은 보여줄, 가져올 값을 말한다.
expect는 뒤에 기대하는 값을 넣어준다. (이렇게 되기를 기대한다~)
여기서 한 글자라도 틀리면 틀렸다고 오류를 돌려보내준다.


그런데 이렇게 하나하나 하면 나중에 내용을 바꿀 때도 번거롭다. 우리는 사진찍는 것처럼 옯기는 snapshot 방법으로 테스트를 한다.

<실습 3> snapshot 방식으로 테스트
기존 사진이 있니? 물어보기.

업데이트 후 yarn test -u를 해주면 사진을 다시 찍는다.

코드를 수정하고 다시 사진을 찍는 전체과정은 아래와 같다.


<실습 4> 버튼을 눌렀을 때, 제대로 작동하는지 테스트하자! (fireEvent)

//  index.tsx
import { useState } from "react";

export default function JestUnitTest() {
  const [count, setCount] = useState(0);

  const onClickCountUp = () => {
    setCount((prev) => prev + 1);
  };

  return (
    <>
      <div role="count">철수는 {count}살 입니다.</div>
      <button role="count-button" onClick={onClickCountUp}>
        카운트 올리기
      </button>
    </>
  );
}


// index.test.tsx
import { useState } from "react";

export default function JestUnitTest() {
  const [count, setCount] = useState(0);

  const onClickCountUp = () => {
    setCount((prev) => prev + 1);
  };

  return (
    <>
      <div role="count">철수는 {count}살 입니다.</div>
      <button role="count-button" onClick={onClickCountUp}>
        카운트 올리기
      </button>
    </>
  );
}

API 검증을 하려할 때 문제점
1. 테스트 시간 오래걸림
2. 백엔드 의존성이 높음

해결
=> 나만의 가짜 백엔드 API를 만든다 (=mock)
나만의 가짜 백엔드 API 만들고 사용(mocking)하기
목킹을 다른말로 목테스트라고도 한다.

<실습 5> 나만의 가짜 백엔드 API만들기 mock

// index.tsx
import { gql, useMutation } from "@apollo/client";
import { useRouter } from "next/router";
import { useState } from "react";

export const 나의그래프큐엘셋팅 = gql`
  mutation createBoard($createBoardInput: CreateBoardInput!) {
    createBoard(createBoardInput: $createBoardInput) {
      _id
      writer
      title
      contents
    }
  }
`;
export default function GraphqlMutationPage() {
  const router = useRouter();
  const [writer, setWriter] = useState("");
  const [title, setTitle] = useState("");
  const [contents, setContents] = useState("");

  const [나의함수] = useMutation(나의그래프큐엘셋팅);

  const onClickSubmit = async () => {
    const result = await 나의함수({
      variables: {
        createBoardInput: {
          writer,
          title,
          contents,
          password: "1234",
        },
      },
    });
    console.log(result);
    const boardId = result.data.createBoard._id;
    if (typeof boardId === "string") void router.push(`/boards/${boardId}`);
  };

  const onChangeWriter = (event) => {
    setWriter(event.target.value);
  };

  const onChangeTitle = (event) => {
    setTitle(event.target.value);
  };

  const onChangeContents = (event) => {
    setContents(event.target.value);
  };

  return (
    <div>
      작성자:
      <input role="input-writer" type="text" onChange={onChangeWriter} />
      제목: <input role="input-title" type="text" onChange={onChangeTitle} />
      내용:
      <input role="input-contents" type="text" onChange={onChangeContents} />
      <button role="submit-button" onClick={onClickSubmit}>
        GRAPHQL-API(동기) 요청하기
      </button>
    </div>
  );
}



// index.test.tsx
// render로 그리고 screen으로 검즘
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
// 돔에 그리는게 아닌 제스트 돔에 그려야하기 때문에 import 해줌
import "@testing-library/jest-dom";
import GraphqlMutationPage, {
  나의그래프큐엘셋팅,
} from "../../pages/34-05-jest-unit-test-mocking";
import { MockedProvider } from "@apollo/client/testing";
import { useRouter } from "next/router";

// 1. 가짜 useRouter 만들고, 그 안에 가짜 push 넣어놓기
jest.mock("next/router", () => ({
  // useRouter를 제스트의 함수로 대체한다.
  useRouter: jest.fn(),
}));

// 2. 이 가짜 push를 useRouter에 집어넣어주어야함
const push = jest.fn();

// 3. 가짜 useRouter에 가짜 push 집어 넣기
(useRouter as jest.Mock).mockImplementation(() => ({
  push,
}));

// 가짜 mutation 만들기(요청, 응답 모두
const mocks = [
  {
    request: {
      query: 나의그래프큐엘셋팅,
      variables: {
        createBoardInput: {
          writer: "철수",
          title: "안녕하세요",
          contents: "반갑습니다",
          password: "1234",
        },
      },
    },
    result: {
      data: {
        createBoard: {
          _id: "qqq",
          wirter: "철수",
          title: "안녕하세요",
          contents: "반갑습니다",
        },
      },
    },
  },
];

it("버튼을 눌렀을 때, 제대로 작동하는지 테스트하자!", async () => {
  render(
    <MockedProvider mocks={mocks}>
      <GraphqlMutationPage />
    </MockedProvider>
  );

  // 타이핑은 change
  fireEvent.change(screen.getByRole("input-writer"), {
    target: { value: "철수" },
  });

  fireEvent.change(screen.getByRole("input-title"), {
    target: { value: "안녕하세요" },
  });

  fireEvent.change(screen.getByRole("input-contents"), {
    target: { value: "반갑습니다" },
  });

  // TDD  => 테스트를 먼저 만들자
  fireEvent.change(screen.getByRole("input-password"), {
    target: { value: 1234 },
  });

  fireEvent.click(screen.getByRole("submit-button"));

  await waitFor(() => {
    expect(push).toBeCalledWith("/boards/qqq");
  });
});

테스트 시에 result의 결과값이 나와야 테스트를 통과할 수 있다.

테스트 코드는 await를 기다리지 않는다.
그래서 waitFor라는 기능을 사용해야한다.

jest에는 router push 가 없어서 가짜 router와 push를 만들어서 그게 실행된다.

정리하면
input change를 바꾸고 그 값이 index.tsx에 variables로 들어가서 mocks와 비교한다.
비교 시 안맞는다면 ? 오류를 보여준다. 맞다면? 가짜푸시가 잘 실행되는지 확인한다.

기능에 패스워드 체인지가 없다. 인풋창을 만들고 온 체인지 페스워드를 작성하고 테스트코드를 작성하거나
먼저 TDD를 하고 기능을 만들자.

회사마다 문화가 다르다.
습관을 만들자!

도메인 가비아 구입!

profile
총총

0개의 댓글