Jest 로 Unit Test 첫 적용기

김태훈·2023년 4월 24일
0

서론

늦게나마 테스트 코드를 적용하게 되었다 !
사실 테스트 코드가 "왜" 반드시 필요한가 에 대한 의문은 조금 없어졌을 뿐 완벽히 없어지진 않았다.

" 회원가입에 테스트 코드가 왜 필요하지? DB 스키마와 일치하지 않으면 알아서 RDBMS가 관리해 주지 않나? "

이런 막연한 생각들이 자리잡았다.
하지만 테스트 코드의 중요성에 관한 내용을 너무 많이 접해서, 이번 프로젝트부터 테스트코드를 직접 짜보기로 하였다. 하다 보면 테스트코드가 왜 필요한가에 대한 의문이 완벽히 해소되길 바라며..!

1. 회원가입 테스트 코드

가장 첫 번째로 테스트코드를 적용시킨 부분은 '회원가입' 이었다.

1) 올바르지 않은 Unit Test

처음에는 어떤 식으로 테스트코드를 구상하고자 하였냐면, Test 용 DB를 따로 두어서 테스트 코드를 작성하고자 하였다.
하지만 이는 결코 좋은 방법이 아니다. 이는 Integration Test에서 진행해야 한다.
내가 하고자 하는 것은 Unit Test이다.

Unit Test
Unit testing is a type of software testing that focuses on individual units or components of a software system. The purpose of unit testing is to validate that each unit of the software works as intended and meets the requirements. Unit testing is typically performed by developers, and it is performed early in the development process before the code is integrated and tested as a whole system.

Integration Test
Integration testing is the process of testing the interface between two software units or modules. It focuses on determining the correctness of the interface. The purpose of integration testing is to expose faults in the interaction between integrated units. Once all the modules have been unit tested, integration testing is performed.

by https://www.geeksforgeeks.org/software-engineering-integration-testing/

말 그대로 "단위 테스트"로써, 통합적인 시스템의 흐름을 테스트하는 것이 아니라 부분부분 독립적으로 테스트 하기 때문에 어떤 코드를 리팩토링할 경우에 빠르게 문제여부를 확인할 수 있게 하는 것이 Unit Test의 목표이다.
따라서, DB의 연동 부분은 "Integration Test"에 맡기는 것이다.

2) 그렇다면 어떻게? -> "Mocking"

1. Mocking 이란 무엇인가? 그리고 무엇을 위해 존재하는가?

Mocking 이란 가짜 객체를 말한다.
그렇다면 왜 가짜 객체가 필요한 지에 대한 본질적인 질문이 필요하다.
앞서 말했듯, 지금 하고자 하는것은 DB에 잘 저장되는지를 보려는 것이 아니라, 소프트웨어 시스템의 각 구성 요소들, 아주 작은 부분부분 요소들이 잘 굴러가는지 테스트를 하는 것이다. DB에 잘 저장되는지는 Integration Test 로 하는 것임을 누차 기억해두자.
생각해보라. DB에 잘 저장되는지 까지 한꺼번에 테스트를 하면, 내가 어디에서 문제가 생겼는지 모르게 된다. 나의 로직에 문제가 있는지, 아니면 DB연결에 문제가 있는지 또 다시 디버깅을 해야할 것이다. Unit 테스트는 SW 구성 요소의 아주 작은 단위를 테스트 하는 것이다.

따라서, 지금과 같이, 테스트를 하고 싶은 기능이 다른 기능과 연관(소프트웨어 unit들이 연관)된 경우에 그러한 연관 구성 요소들을 Mocking이라는 가짜 객체로 대체하는 것이 필요한 것이다. 우리는 회원가입과 관련된 Test를 진행중임으로, Unit Test를 하기 위해서 DB 저장 과정을 Mocking하여 대체하는 것이다. 이는 "DB에 잘 저장 될 것"임을 가정하며 시작한다.

회원가입을 진행하기 위해서는 다음이 필요하다.

  • Request : ID, EMAIL, PASSWORD

이러한 요청을 POST로 보내면, 서버에서는 유효성 검사 or 중복 검사를 거치고, 최종적으로 DB에 저장되며, Request에 알맞은 Response를 클라이언트로 보낼 것이다.
이 때, DB에 저장되는 과정을 Mocking이 대체하며, Response 까지 이어서 Mocking이 그 역할을 한다.

2. Mocking 적용법

Mocking 관련 함수들을 정리해놓았다.

https://inpa.tistory.com/entry/JEST-%F0%9F%93%9A-%EB%AA%A8%ED%82%B9-mocking-jestfn-jestspyOn

3) 코드 적용

1. 회원가입 라우터

router.post('/signup', userUtil.signUp);

2. userUtil 미들웨어

해당 signUp 함수를 Post 메소드의 미들웨어로 작성한 것이다.

const user = {
  signUp: async (req, res) => {
    try {
      const success = await sql.signupUser(req);
      if (success) {
        return res.status(200).send({ success: true });
      } else {
        throw new Error();
      }
    } catch (error) {
      return res.status(403).send({ success: false });
    }
  },
};

3. test함수 (Jest 이용)

jest.mock('../database/sql.js');
import sql from '../database/sql.js';
import * as userTest from '../validation/user.js';
import userUtil from '../middleware/user.js';

describe('회원가입 체크', () => {
  const res = {
    status: jest.fn(() => res), //체이닝 하므로 자기자신
    send: jest.fn(),
  };

  //테스트 분기 1
  test('회원 가입 성공', async () => {
    // User 정보 create return값 강제 지정
    let check = true;
    const req = {
      nickname: 'testNickname',
      email: 'testcode@gmail.com',
      password: 'a12345678',
    };
    if (userTest.checkPassword(req.password) === false) {
      check = false;
    }
    if (userTest.checkEmail(req.email) === false) {
      check = false;
    }
    if (check) {
      sql.signupUser.mockReturnValue(true);
      await userUtil.signUp(req, res);
      expect(res.send).toBeCalledWith({ success: true });
    }
  });
});

1. jest.mock("../database/sql.js")

database/sql.js 모듈 내의 모든 함수들을 mocking한다. SQL DB 저장 관련 로직들을 Mocking 한다. 가장 작은 Unit Test를 진행하기 위해서이다.

2. import sql from "../database/sql.js";

먼저 mock처리를 하고 난 후에 해당 모듈 함수들을 import 한다. mock을 먼저해야 함을 잊지말자.

3. const res = { status: jest.fn(() => res), ...}

다음은 jest가 제공하는 함수들로, jest.fn 을 이용하여 Mock Function을 정의할 수 있다. 이렇게 정의하면 status는 자기 자신을 포함하는 객체(res)를 반환하는 Mock함수가 정의 된다.

4. 모든 유효성 검사를 거치고, 검사가 완료 되면? ~ if (check) ~

sql.signupUser.mockReturnValue(true);
이 코드는 "sql 모듈에 존재하는 signupUser 의 함수의 return 값을 true로 한다." 는 뜻이다.
우리는 이미 sql모듈 전체를 Mocking하였기 때문에 mockReturnValue로 반환값을 지정할 수 있다. 그 후에 userUtil.signUp(req,res)를 실행하면, userUtil 내에서

try {
  const success = await sql.signupUser(req);
  if (success) {
    return res.status(200).send({ success: true });
  } else {
    throw new Error();
  }
}

success에 true값이 들어가게 되고, 결국 반환값인 {success: true}가 나오고
이를 expect로 검사한다.

profile
기록하고, 공유합시다

0개의 댓글