본문 바로가기

내용 복습/Node.js

Jest 학습 1일차

이번에 jest 강의를 듣게 되었는데, 영문 강의라 번역을 다시 하는 불상사가 없도록 강의내용을 정리해보려 한다.

 

1. Jest를 왜 써야하는가?

  • 소프트웨어 테스트는 단순히 다른 코드를 실행하는 코드이고 소프트웨어 테스트는 단순히 다른 코드를 실행하고 어설션 또는 검사를 수행하는 코드입니다. 여기서 어설션이란 테스트를 위해 가정한 논리식으로 개발자가 참이어야 테스트를 통과하도록 지정한 식을 말한다.
  • 단일 테스트와 단위 테스트에 대한 가장 좋은 정의는 요구사항이나 명세서의 역할을 한다는 것이다.
  • 유닛테스트는 가장 작은 단위로 볼 수도 있겠지만, 메소드, 클래스, 모듈이라고 생각할 수도 있다. 따라서 단위는 별개가 아닌 함께 테스트 할 수 있는 작은 단위의 조합으로 볼 수 있다.
  • 가장 큰 장점은 테스트를 진행하면서 코드 안정성이 크게 증가하게 된다. 잘못된 코드로 애플리케이션이 실행될 것을 미연에 방지할 수 있어 버그를 빨리 찾아낼 수 있다.
  • 그리고 단위 테스트의 코드 작성이 매우 어렵다면 그 코드 자체에 문제가 있을 가능성이 높다. 또한, 코드 단위로 테스트함으로써 QA나 유저테스트 등 다른 방식의 테스트에 드는 비용을 줄일 수 있다.

 

2. Jest 기초(with Typescript)

  •  Jest는 facebook에서 개발한 Javascript, Typescript 테스트 프레임워크이다.
  • 또한 테스트를 실행하는 역할을 하며, 테스트의 설명, 기댓값 등을 포함하는 전역함수이기도 하다. 따라서 파일에 디펜던시를 설치하면 import문 없이 테스트파일에서 사용할 수 있다는 뜻이다.
  • Jest는 어설션 라이브러리이며, 매우 강력한 matchers 세트를 포함한다. Jest는 JS, TS 테스트 툴중 가장 높은 사용률을 자랑하며 지원하는 기능도 많아 일반적으로 사용되는 툴이다.
  • 위 내용을 종합했을 때, Jest는 allinone 솔루션으로 runner, assertion, matcher의 역할을 모두 맡을 수 있다는 큰 장점이 있는 것이다. 또한 typescript도 지원한다.

1. jest ts 프로젝트 셋업

npm init -y

npm i -D typescript jest ts-jest @types/jest ts-node

- jest.config.js 자동생성(우리의 경우 ts라 직접 파일을 생성하여 설정코드 작성해야)
npx ts-jest config:init

 

// jest.config.ts
import type { Config } from '@jest/types'

const config: Config.InitialOptions = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  verbose: true
}

export default config;
// tsconfig.json
{
  "compilerOptions": {
    "esModuleInterop": true
  }
}

여기까지 jest에서 ts를 사용하기 위한 기본 설정을 마쳤다. 다음은 간단한 함수를 개별테스트 해보겠다.

 

// src/app/Utils.ts 대문자로 바꾸는 단순한 함수 체크 
export function toUpperCase(arg: string) {
  return arg.toUpperCase();
}

 

// src/test/Utils.test.ts
// 두번째 인자로 콜백함수를 가지는 것을 유의
// 위의 코드를 테스트하는 코드
// 잘 export 했다면 자동 import됨
import { toUpperCase } from "../app/Utils";

// describe 메소드로 그룹화 
describe("Utils test suite", () => {
// 개별 테스트 케이스를 설명
  test("should return uppercase", () => {
    const result = toUpperCase("abc");
    // test의 단언문(assertions)으로 결과를 예상값과 비교
    expect(result).toBe("ABC");
  });
});

 

 

- 트리플 A 원칙(AAA principles)
TDD(테스트 주도개발)에서 꼭 지켜야 하는 원칙 3가지로, 효과적인 테스트케이스를 작성하여 개발하는 데 도움이 된다.
1. Arrange : 테스트 환경 설정
이 단계에서는 테스트에 필요한 초기화 작업을 수행하고, 테스트 대상 객체를 생성하거나 설정한다.
테스트에 필요한 객체, 변수, 또는 데이터를 생성하고 준비하여 필요한 상태나 조건을 설정한다.

2. Act: 단계에서는 테스트 대상 코드를 호출하거나 실행
이 단계는 Arrange에서 준비한 환경과 데이터를 사용하여 테스트 대상 코드를 호출하고, 그 결과를 얻는다.

3. Assert:예상한 대로 동작하는지 확인
Act 단계에서 실행한 코드의 결과를 검증하고, 테스트가 성공적으로 통과되었는지 여부를 판단하게 된다.
결과 값을 검증하여 예상한 결과와 일치하는지 확인하고 예상한 결과가 아니라면 테스트는 실패한다.

 

원칙에 따라 원래 코드를 수정하면 아래와 같이 단계를 나눌 수 있다. sut는 system under test의 약자이고 result를 actual이라는 실제 값으로 변경했다. 그리고 expected 상수를 추가한 것을 알 수 있다. test 메소드를 별칭을 나타내는 it 메소드로 대체했다.

import { toUpperCase } from "../app/Utils";

describe("Utils test suite", () => {
  it("should return uppercase of valid string", () => {
    // arrange
    const sut = toUpperCase;
    const expected = "ABC";

    // act
    const actual = sut("abc");

    // assert
    expect(actual).toBe(expected);
  });
});

 

toBe는 Jest의 가장 기본적인 matcher로, 두 string 값을 비교한다.

 

2. type 정의와 toEqual

utils에 아래 내용을 추가해준다. 객체 형태로 타입을 정의하고 그것을 이용해서 여러 형태로 변환하는 함수를 생성했다.

export type stringInfo = {
  lowerCase: string;
  upperCase: string;
  characters: string[];
  length: number;
  extraInfo: Object | undefined;
};

export function getStringInfo(arg: string): stringInfo {
  return {
    lowerCase: arg.toLowerCase(),
    upperCase: arg.toUpperCase(),
    characters: Array.from(arg),
    length: arg.length,
    extraInfo: {},
  };
}

 

  // only를 추가하면 이 테스트만 실행한다.
  it.only("should return info for valid string", () => {
    const actual = getStringInfo("My-String");

    expect(actual.lowerCase).toBe("my-string");
    expect(actual.extraInfo).toBe({});
  });

test 파일에 이 코드를 추가하고 실행하면 다음과 같은 에러를 마주할 수 있다.

 

 

읽어보면 toBe를 toEqual로 바꿔야 한다고 하고, only를 붙여줘서 나머지 하나의 테스트는 스킵한 것을 볼 수 있다. 원시타입으로 작업하면 matcher로써나 비교 연산자로써 toBe를 사용할 수 있다. 하지만 객체가 포함되어 있으면 toEqual을 사용해야한다.

그리고 toHaveLength(9)나 toContain('M') 등의 방법으로 여러 테스트를 만들 수 있다. 특히 toContain은 서로 다른 Array에 같은 값이 들었는지 확인할 때 유용하고 타입스크립트를 사용하기에 제네릭 타입도 적용할 수 있다.

또, toEqual 내부에 expect.arrayContaining 추가해서 subArray를 확인하는 것도 가능하다. 마지막으로 같은 테스트라도 특정한 메소드를 사용해 정확히 해주는게 좋다. 아래와 같은 적절한 메소드를 선택하는 것도 중요하다.

    expect(actual.characters.length).toBe(9);
    expect(actual.characters).toHaveLength(9);
    
    expect(actual.extraInfo).not.toBe(undefined);
    expect(actual.extraInfo).not.toBeUndefined();

3. each 키워드

  • each 함수는 인수 배열을 받습니다. 이 배열의 각 항목에 대해 테스트가 실행됩니다.
  • 이 배열의 각 항목에 대해 테스트가 실행됩니다. 이 jest 기능의 멋진 점은 테스트 설명에서도 이를 명시할 수 있다는 것입니다.
  • 이처럼 매개변수를 사용해 단언문의 설명도 아래처럼 삽입되어 출력되고, 변수로 전달받아 정상적으로 테스트할 수 있다.
  • 이 방식을 이용하면 많은 사용 사례를 테스트할 때 중복성을 줄이는 방법을 살펴보았습니다. 우리는 each 함수를 사용하여 다양한 매개변수를 테스트에 전달할 수 있으며, 이를 통해 훨씬 더 의미 있는 테스트를 작성하고, 적은 코드로 많은 기능을 구현할 수 있습니다.
  describe.only("ToUpperCase examples", () => {
    it.each([
      { input: "abc", expected: "ABC" },
      { input: "My-String", expected: "MY-STRING" },
      { input: "def", expected: "DEF" },
    ])("$input toUpperCase should be $expected", ({ input, expected }) => {
      const actual = toUpperCase(input);
      expect(actual).toBe(expected);
    });
  });