[ 2024.09.27 TIL ] 테스트 코드

박지영·2024년 9월 27일
0

Today I Learned

목록 보기
52/88

계층형 아키텍처 패턴

💻 테스트 코드

테스트 코드란?

테스트 코드란 작성한 코드가 문제가 없는지 테스트하기 위해 작성하는 코드.

  • 테스트 코드의 목적은 내 코드가 멀쩡하다! 를 증명하기 위해서 작성하는 것이 아니라 코드의 결과확인하고 검증하기 위해 작성한다.

테스트 코드의 종류

  • 단위 테스트(Unit Test)

    • 가장 작은 규모의 기능을 테스트
  • 통합 테스트(Integration Test)

    • 다양한 기능을 합쳤을 때 생기는 문제를 테스트
  • E2E 테스트(End-to-end Test)

    • 끝에서 끝, 즉 백엔드부터 최종적으로 웹 페이지까지 동작하며 원하는 데이터를 보여주는지 테스트

테스팅 프레임워크 Jest

  • 페이스북에서 개발했으며 react와 궁합이 잘 맞아 급격하게 성장한 테스팅 프레임워크이다.

  • Nest.js에서 기본 지원하는 테스팅 프레임워크이다.

  • Jest테스트 코드의 표현이 다른 프레임워크 보다 훨씬 간결하다. -> 빠르게 작성하고 빠르게 테스트 가능하다.

  • 사용 등록: yarn test

    package.json
    ...
    "type": "module",
      "scripts": {
        "test": "node --experimental-vm-modules node_modules/.bin/jest"
      },
    ...

Jest를 이용한 Unit Test

  • 관례상 테스트할파일이름.spec.js <- 형식으로 파일명을 작성한다.

  • test() 단위 테스트를 묶어주는 함수.

  • expect() 특정 값이 만족되는지 확인하기 위한 표현식을 작성할 수 있게 해주는 함수.

직접 해보기

  • 이메일 유효성 검증 테스트
export const isEmail = (value = "") => {
  const [local, domain] = value.split("@");
};
  1. 입력한 이메일 주소에는 @ 문자가 1개만 있어야 이메일 형식이다.
  if (!value || value.split("@").length !== 2) return false;
  1. 입력한 이메일 주소에 공백(이 존재하면 이메일 형식이 아니다.
  if (value.includes(" ")) return false;
  1. 입력한 이메일 주소 맨 앞에 하이픈(-)이 있으면 이메일 형식이 아니다.
  if (value[0] === "-") return false;
  1. 입력한 이메일 주소 중 로컬 파트(골뱅이 기준 앞부분)에는 영문 대소문자와 숫자, 특수문자는 덧셈기호(+), 하이픈(-), 언더바(_) 3개 외에는 다른 값이 존재하면 이메일 형식이 아니다.
  if (!/^[a-zA-Z0-9+-_.]+$/.test(local)) return false;
  1. 입력한 이메일 주소 중 도메인(골뱅이 기준 뒷부분)에는 영문 대소문자와 숫자, 점(.), 하이픈(-)외에 다른 값이 존재하면 이메일 형식이 아니다.
  if (!/^[a-zA-Z0-9.-]+$/.test(domain)) return false;
  • 테스트 케이스
test("입력한 이메일 주소에는 @ 문자가 1개만 있어야 이메일 형식이다.", () => {
  expect(isEmail("myemail@domain.com")).toEqual(true);
  expect(isEmail("myemail@@domain.com")).toEqual(false);
  expect(isEmail("@myemail@domain.com")).toEqual(false);
  expect(isEmail("my@email@domain.com")).toEqual(false);
  expect(isEmail("myemail@doma@in.com")).toEqual(false);
  expect(isEmail("myemail@domain.com@")).toEqual(false);
});

test("입력한 이메일 주소에 공백(이 존재하면 이메일 형식이 아니다.", () => {
  expect(isEmail("myemail@domain.com")).toEqual(true);
  expect(isEmail("my email@domain.com")).toEqual(false);
  expect(isEmail("myemail@do main.com")).toEqual(false);
  expect(isEmail("myemail@domain. com")).toEqual(false);
});

test("입력한 이메일 주소 맨 앞에 하이픈(-)이 있으면 이메일 형식이 아니다.", () => {
  expect(isEmail("myemail@domain.com")).toEqual(true);
  expect(isEmail("myemail@doma-in.com")).toEqual(true);
  expect(isEmail("-myemail@domain.com")).toEqual(false);
  expect(isEmail("myem--ail@domain.com")).toEqual(true);
  expect(isEmail("my-email@domain.com")).toEqual(true);
});

test("입력한 이메일 주소 중 로컬 파트(골뱅이 기준 앞부분)에는 영문 대소문자와 숫자, 특수문자는 덧셈기호(+), 하이픈(-), 언더바(_) 3개 외에는 다른 값이 존재하면 이메일 형식이 아니다.", () => {
  expect(isEmail("myemail@domain.com")).toEqual(true);
  expect(isEmail("my+-Emai_l@domain.com")).toEqual(true);
  expect(isEmail("my^^e%m&a!il@domain.com")).toEqual(false);
  expect(isEmail("mye+_*^&)mail@domain.com")).toEqual(false);
  expect(isEmail("mye#!?>L:mail@domain.com")).toEqual(false);
});

test("입력한 이메일 주소 중 도메인(골뱅이 기준 뒷부분)에는 영문 대소문자와 숫자, 점(.), 하이픈(-)외에 다른 값이 존재하면 이메일 형식이 아니다.", () => {
  expect(isEmail("myemail@domain.com")).toEqual(true);
  expect(isEmail("myemail@doMa-in.com")).toEqual(true);
  expect(isEmail("myemail@d-o231ma-in.com")).toEqual(true);
  expect(isEmail("myemail@doma^!#in.com")).toEqual(false);
  expect(isEmail("myemail@doma>?in.com")).toEqual(false);
});
  • 결과
 PASS  ./validation.spec.js
  √ 입력한 이메일 주소에는 @ 문자가 1개만 있어야 이메일 형식이다. (3 ms)
  √ 입력한 이메일 주소에 공백(이 존재하면 이메일 형식이 아니다. (1 ms)
  √ 입력한 이메일 주소 맨 앞에 하이픈(-)이 있으면 이메일 형식이 아니다. (1 ms)
  √ 입력한 이메일 주소 중 로컬 파트(골뱅이 기준 앞부분)에는 영문 대소문자와 숫자, 특수문자는 덧셈기호(+), 하이픈(-), 언더바(_) 3개 외에는 다른 값이 존재하
면 이메일 형식이 아니다. (1 ms)
  √ 입력한 이메일 주소 중 도메인(골뱅이 기준 뒷부분)에는 영문 대소문자와 숫자, (.), 하이픈(-)외에 다른 값이 존재하면 이메일 형식이 아니다. (1 ms)       

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        0.667 s, estimated 1 s
Ran all test suites.

Jest Cli option 및 문법

Cli option

  • --forceExit

    • 테스트 코드 검사가 완료되었을 때, 강제로 Jest를 종료한다.
    • expressapp 객체와 Prisma 연결이 Connect상태로 남아있어 테스트 코드가 종료되지 않을 때 사용한다.
  • --silent

    • 테스트 코드를 실행했을 때, console.log와 같은 메시지를 출력하지 않는다.
  • --coverage

    • 테스트 코드 검사가 완료된 후 현재 프로젝트의 테스트 코드 커버리지를 출력해준다.
  • --verbose

    • 테스트 코드의 개별 테스트 결과를 표시해준다.

expect 결과값 검증

  • .mockReturnValue(value)

    • Mock 함수의 반환값을 지정한다.
  • .toBe(value)

    • 입력받은 예상값과 결과값이 일치하는지 비교한다.
    • 만약 객체 인스턴스를 비교하려 한다면, 인스턴스 ID까지 비교하므로 엄격하게 동일한지 검증한다.
  • .toEqual(value)

    • 입력받은 예상값과 결과값이 일치하는지 비교한다.
  • .toMatch(regexp | string)

    • 입력받은 문자열이 결과값과 같은지 검증한다.
    • String 또는 정규표현식으로 검증할 수 있다.
  • .toBeTruthy()

    • 결과값이 true인지 검증한다.
  • .toBeInstanceOf(Class)

    • 입력받은 예상값과 Class가 동일한 Instance인지 검증한다.
    • Error를 검증할 때 주로 사용한다.
  • .toHaveProperty(keyPath, value?)

    • 입력받은 객체의 KeyValue가 일치하는지 검증한다.
  • .toMatchObject(object)

    • 입력받은 객체와 결과 객체가 일치하는지 검증한다.
    • 만약, 입력받은 객체에는 없지만, 결과 객체에 있는 속성이 있다면 이를 무시하고 일치 여부를 확인한다.

global Jest 문법

  • afterAll(fn, timeout)

    • 모든 test()완료된 이후에 수행된다.

    • 테스트가 완료된 이후 DB에 변경된 데이터를 삭제하거나 Mock을 초기화 하기 위해 사용된다.

  • afterEach(fn, timeout)

    • test()완료된 이후에 수행된다.

    • 테스트코드가 완료된 이후 Mock 또는 변경된 전역 변수를 초기화할 때 사용된다.

  • beforeAll(fn, timeout)

    • 테스트 코드실행되기 전 최초로 수행된다.

    • DB의 데이터를 초기화하거나 전역 Mock을 초기화할 때 사용된다.

  • beforeEach(fn, timeout)

    • test()실행되기 전에 수행된다.

    • 테스트가 실행되기 전, 동일한 설정을 반복해야할 때 사용된다.

Mock function

Mock은 특정 메소드나 함수를 Mocking하기 위해 사용된다.

  • 테스트에서 시간 또는 비용이 많이 들거나, 의존성이 높은 코드를 직접 실행하지 않고, 호출 여부, 입력한 값의 일치 여부 등을 확인하기 위한 가짜 객체이다.

  • 테스트를 하기 위해 매번 DB에 접근하여 데이터를 수정하거나 전송하는 등 비용이 발생하는 작업을 반복할 수 없다. 문제가 발생하거나 로직을 검사하는데 방해가 되는 코드를 실제로 실행한 것 처럼 만들기 위해 Mock이라는 가짜 객체를 사용한다.

  • Mock 객체의 특정 메소드가 몇 번 호출되었는지, 어떤 값을 전달받았는지, 전달받은 데이터의 형식은 내가 생각한 것이 맞는지 등 다양한 조건을 검사할 수 있다.

  • 자주 사용하는 Mock expect 문법

    • .toHaveBeenCalledTimes(number)

      • Mock이 몇번 호출되었는지 검증한다.
    • .toHaveBeenCalledWith(arg1, arg2, ...)

      • 어떤 인자를 이용해 Mock이 호출되었는지 검사한다.

의존성 주입(Dependency Injection, DI)

의존성 주입이란 객체 사이의 의존 관계를 외부에 제공하는 방법.

  • 단위 테스트 코드를 잔석할 때, 레포지토리 계층의 DB를 Mocking하여 실제 DB에 접근하지 않고 테스트 코드를 수행할 수 있어야 한다.

📌 Jest의 window 환경에서 import 불가능 문제

"type": "module",
  "scripts": {
    "test": "node --experimental-vm-modules node_modules/.bin/jest"
  },

강의에 나온 스크립트 설정을 했더니

PS D:\jest-test> yarn test
yarn run v1.22.22
$ node --experimental-vm-modules node_modules/.bin/jest
D:\jest-test\node_modules\.bin\jest:2
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
          ^^^^^^^

SyntaxError: missing ) after argument list
    at wrapSafe (node:internal/modules/cjs/loader:1378:20)
    at Module._compile (node:internal/modules/cjs/loader:1428:41)
    at Module._extensions..js (node:internal/modules/cjs/loader:1548:10)
    at Module.load (node:internal/modules/cjs/loader:1288:32)
    at Module._load (node:internal/modules/cjs/loader:1104:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12)
    at node:internal/main/run_main_module:28:49

Node.js v20.17.0
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

이런 오류가 떴다. 대충 Unix 계열의 명령어를 사용해서 window 환경에서는 이 명령어가 작동하지 않기 때문에 오류가 발생한다는 뜻이다.

그래서 윈도우 환경에서는

"type": "module",
  "scripts": {
    "test": "jest"
  },

이렇게 사용해야한다는 내용을 찾았다.

하지만 왜인지 저렇게 사용하니 jest가 ES6의 import는 문법을 해석할 수 없는 문제가 생겼다.

yarn run v1.22.22
$ jest
 FAIL  ./validation.spec.js
  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    D:\jest-test\validation.spec.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { isEmail } from "./validation";
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1505:14)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.291 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

헤멘 끝에 한 사이트의 글을 발견했는데
출처: https://poiemaweb.com/jest-esm

yarn add -D jest @types/jest @babel/core @babel/preset-env

ts/jest와 babel을 설치해야하고

babel은 ES6문법을 ES6 이전 문법으로 변환시켜주는 역할을 한다.
babel.config.json을 생성해서

{
  "presets": ["@babel/preset-env"]
}

을 붙여넣고

jest.config.js를 생성하고

export default {
  transform: {
    "^.+\\.js$": "babel-jest",
  },
  testEnvironment: "node",
};

를 붙여넣는다.

이후 테스트

 PASS  ./validation.spec.js
  √ 입력한 이메일 주소에는 @ 문자가 1개만 있어야 이메일 형식이다. (3 ms)
  √ 입력한 이메일 주소에 공백(이 존재하면 이메일 형식이 아니다. (1 ms)
  √ 입력한 이메일 주소 맨 앞에 하이픈(-)이 있으면 이메일 형식이 아니다. (1 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        0.637 s, estimated 1 s
Ran all test suites.

정상적으로 작동하는 모습.

profile
신입 개발자

0개의 댓글