Jest | 테스트 전/후 작업, describe, only, skip

Kate Jung·2022년 4월 6일
1

Front-end Test

목록 보기
4/17
post-thumbnail

테스트를 작성하다보면 테스트 전후에 해줘야될 작업들이 생김.
→ 이런 처리 가능 하도록 Jesthelp 함수 제공

🔍 테스트 방법

npm test

🔍 공통 사항

  • [옵션] timeout(milliseconds) 사용 가능
    • 기본 timeout : 5초

      → 해당: beforeEach/All, afterEach/All, only

<테스트 전/후 작업>

🔸 예시 코드 1 (Each의 필요성)

각 테스트 실행 직전, 초기화 해주는 과정이 필요 할 때

🔹 1. 기본

변수 num 을 만들고 0을 넣어줌. 그리고 add 함수 테스트.

  • add 함수 (fn.js)
    const fn = {
      add: (num1, num2) => num1 + num2,
      (...생략)
    };
    
    module.exports = fn;
  • 테스트 코드 작성 (fn.test.js)

    성공

    const fn = require("./fn");
    
    let num = 0; // 👈
    
    test("0 더하기 1은 1이야", () => {
      num = fn.add(num, 1); // 👈
      expect(num).toBe(1);
    });

🔹 2. 테스트 추가

  • fn.test.js

    const fn = require("./fn");
    
    let num = 0;
    
    test("0 더하기 1은 1이야", () => {
      num = fn.add(num, 1); // 👈 num = 1
      expect(num).toBe(1);
    });
    
    test("0 더하기 2은 2이야", () => {
      num = fn.add(num, 2); // 👈 num = 3
      expect(num).toBe(2);
    });
    
    test("0 더하기 3은 3이야", () => {
      num = fn.add(num, 3); // 👈 num = 6
      expect(num).toBe(3);
    });
    
    test("0 더하기 4은 4이야", () => {
      num = fn.add(num, 4); // 👈 num = 10
      expect(num).toBe(4);
    });
  • 테스트 결과

    첫 번째: 성공. 나머지: 실패.

    • 원인

      num에 계속 새로운 값이 할당됨.

      num(받은 값)이 0으로 시작해서 두번째 테스트부터 3, 6, 10

    • 터미널

  • 해결 방법

    각 테스트 실행 직전, num을 초기화 해주는 과정이 필요 → beforeEach 활용

📌 beforeEach(fn, timeout)

이 파일의 각 테스트가 실행되기 전에 함수를 실행

  • 유용한 경우

    많은 테스트에서 사용될 global statereset하려는 경우

  • describe 블록 내부에 있을 경우

    describe 블록의 각 테스트에 대해 실행됨.

  • 일부 설정 코드를 한 번만 실행해야 하는 경우

    테스트가 실행되기 전에 beforeAll을 대신 사용할 것.

  • 함수가 promise를 반환하거나 generator인 경우

    Jest는 test를 실행하기 전에 해당 promiseresolve될 때까지 기다림.

🔹 [예시 코드 1-2] 의 문제 해결

beforeEach 활용 (num을 0으로)

  • 테스트 (fn.test.js)

    const fn = require("./fn");
    
    let num = 0;
    
    beforeEach(() => { // 👈 문제 해결
      num = 0;
    });
    
    test("0 더하기 1은 1이야", () => {
      num = fn.add(num, 1);
      expect(num).toBe(1);
    });
    
    (...생략 (test 2, 3, 4))
  • 테스트 결과

    모두 통과

  • 초기값 바꾸기 가능

    • 테스트 (fn.test.js)

      const fn = require("./fn");
      
      let num = 10; // 👈 초기값 10으로 수정
      
      beforeEach(() => {
        num = 0;
      });
      
      test("0 더하기 1은 1이야", () => {
        num = fn.add(num, 1);
        expect(num).toBe(1);
      });
      
      (...생략 (test 2, 3, 4))
    • 테스트 결과

      모두 통과

      • 통과 이유

        첫 번째 테스트 실행 전, 0으로 할당되기 때문

📌 afterEach(fn, timeout)

이 파일의 각 테스트가 완료된 후 함수를 실행

  • 유용한 경우

    각 테스트에서 생성된 임시 state를 정리하려는 경우

  • describe 블록 내부에 있을 경우

    describe 블록 내부에 있는 테스트 후에만 실행

  • 모든 테스트가 실행된 후, 일부 정리를 한 번만 실행 하려면

    afterAll을 대신 사용

  • 함수가 promise를 반환하거나 generator인 경우

    Jest는 계속하기 전에 해당 promiseresolve될 때까지 기다림.

🔹 [예시 코드 1-2] 적용

  • 적용 (fn.test.js)
    const fn = require("./fn");
    
    let num = 10;
    
    afterEach(() => { // 👈
      num = 0;
    });
    
    test("0 더하기 1은 1이야", () => {
      num = fn.add(num, 1);
      expect(num).toBe(1);
    });
    
    (...생략 (test 2, 3, 4))
  • 테스트 결과

    첫 번째 test case만 불통

    • 이유

      초기 값이 10이기 때문

      0으로 바뀌는 타이밍은 첫 번째 테스트 이후 → 첫 번째 테스트는 불통 나머지 테스트는 각 테스트가 끝날 때마다 0으로 바뀜 → 통과
    • 터미널

🔸 예시 코드 2 (All의 필요성)

최초, 최후 각각 한 번씩 테스트 하는 것이 나은 경우

  • 전후 작업의 시간이 좀 걸리는 경우라면?

    테스트 수가 늘어날 수록 테스트 소요 시간도 증가함.

    예를 들어
    • 테스트 전, userDB에 접속해서 user정보를 가지고 오고
    • 테스트 이후, DB connection을 끊는 작업
    • 각 작업은 0.5초 정도 걸린다고 가정

🔹 1. 2가지 함수 제작 & 적용

  • 2가지 함수 제작 (fn.js)
    const fn = {
      (...생략)
      connectUserDb: () => { // 👈 userDb에 접속을 해서 
        return new Promise((res) => {
          setTimeout(() => {
            res({ // 👈 user 정보를 가져옴.
              name: "Mike",
              age: 30,
              gender: "male",
            });
          }, 500); // 👈 0.5초 정도 걸리게 함.
        });
      },
      disconnectDb: () => { // 👈 DB 연결 끊기
        return new Promise((res) => {
          setTimeout(() => {
            res();
          }, 500); // 👈 0.5초 정도 걸리게 함.
        });
      },
    };
    
    module.exports = fn;
  • 테스트에 적용 (fn.test.js)
    const fn = require("./fn");
    
    let user;
    
    beforeEach(async () => { // 👈 작업 전, DB에 접속해서 user 데이터 가져옴.
      user = await fn.connectUserDb();
    });
    
    afterEach(() => { // 👈 작업 후, DB connection 끊음.
      return fn.disconnectDb();
    });
    // 👆 위의 각 작업은 0.5초가 걸림
    
    test("이름은 Mike", () => { // 👈 테스트
      expect(user.name).toBe("Mike");
    });
  • 테스트 결과

    소요 시간: 1초가 넘음

    즉, 이 테스트에 1초가 소요되는 것

🔹 2. 테스트 추가

  • fn.test.js

    const fn = require("./fn");
    
    let user;
    
    beforeEach(async () => {
      user = await fn.connectUserDb();
    });
    afterEach(() => {
      return fn.disconnectDb();
    });
    
    test("이름은 Mike", () => {
      expect(user.name).toBe("Mike");
    });
    test("나이는 30", () => {    // 👈 테스트 추가
      expect(user.age).toBe(30);
    });
    test("성별은 남성", () => {   // 👈 테스트 추가
      expect(user.gender).toBe("male");
    });
  • 테스트 결과

    테스트 시간: 4초

    4초 걸린 이유: 각 테스트 전후에 1초 (0.5초 + 0.5초)가 소요 되기 때문

🔹 3. 문제점 & 해결

  • 문제점

    이런 방식으로 test case가 늘어나면 안됨.

  • 해결

    사실 DB는 한 번만 연결해서 user 정보를 가지고 오고 모든 테스트를 마친 후에 끊어도 상관 없음. 최초, 최후에 각각 한 번씩 해주는 게 더 나음.

    user 정보는 한 번만 가져오고 재활용하면 됨.

    이 때 사용할 수 있는 것이 beforeAll, afterAll

📌 beforeAll, afterAll

test 전체 실행 전 / 후 (각 테스트 케이스마다 실행되는 것이 아님)

🔹 beforeAll(fn, timeout)

  • 유용한 경우

    많은 테스트에서 사용할 global state를 설정(set up)하려는 경우

  • describe 블록 내부에 있을 경우

    describe 블록의 시작 부분에서 실행

  • 모든 테스트 전 마다 무언가 실행 하려면

    beforeEach 사용하라.

  • 함수가 promise를 반환하거나 generator인 경우

    Jest는 test를 실행하기 전에 해당 promiseresolve될 때까지 기다림.

🔹 [예시 코드 2-2] 적용

  • fn.test.js
    (...생략)
    
    beforeAll(async () => { // 👈 수정
      user = await fn.connectUserDb();
    });
    afterAll(() => { // 👈 수정
      return fn.disconnectDb();
    });
    
    (...생략)
  • 테스트 결과

    소요 시간: 1.9초 (훨씬 짧아짐)

    전체 테스트 전후로 약 1초만 추가적으로 소요됨

</테스트 전/후 작업>

📌 describe(name, fn)

비슷한 기능끼리 묶을 수 있음. (여러 관련 테스트를 그룹화하는 블록을 생성)

  • describe 내부의 beforeafter

    describe 내부에서만 동작

🔹 [예시 코드 2-2]에 추가

  1. carDb 함수 제작(fn.js)

    const fn = {
      (...생략)
    
      connectCarDb: () => {
        return new Promise((res) => {
          setTimeout(() => {
            res({
              brand: "bmw",
              name: "z4",
              color: "red",
            });
          }, 500);
        });
      },
      disconnectCarDb: () => {
        return new Promise((res) => {
          setTimeout(() => {
            res();
          }, 500);
        });
      },
    };
    
    module.exports = fn;
  2. 비슷한 기능 끼리 묶기 (fn.test.js)

    describe 밖과 동일하게 작성함.

    const fn = require("./fn");
    
    (...생략)
    
    // 👇 비슷한 기능(자동차 관련 작업) 끼리 묶음
    describe("Car 관련 작업", () => { // 👈 설명 작성
      let car;
    
      beforeAll(async () => {
        car = await fn.connectCarDb();
      });
      afterAll(() => {
        return fn.disconnectCarDb();
      });
    
      test("이름은 z4", () => {
        expect(car.name).toBe("z4");
      });
      test("브랜드는 bmw", () => {
        expect(car.brand).toBe("bmw");
      });
      test("색상은 red", () => {
        expect(car.color).toBe("red");
      });
    });
  3. 테스트 결과

    설명과 depth로 구분 됨

🔸 실행 순서

  • describe 외부와 내부로 나눔.
  • 외/내부에 각각 test, beforeEach, beforeAll, afterEach, afterAll가 있음.

🔹 기억해야 하는 포인트

  • 밖 beforeEach / afterEach 는 이 페이지에 있는 모든 test 실행 전 / 후에 항상 실행

    밖 beforeEach : 내부beforeEach 보다 항상 먼저 실행 ⭐️

    밖 afterEach : 내부 afterEach 가 끝난 후 실행 ⭐️

    밖 beforeEach안 beforeEachtest안 afterEach밖 afterEach

🔹 fn.test.js

const fn = require("./fn");

beforeAll(() => console.log("밖 beforeAll")); // 1
beforeEach(() => console.log("밖 beforeEach")); // 2, 6
afterEach(() => console.log("밖 afterEach")); // 4, 10
afterAll(() => console.log("밖 afterAll")); // 마지막

test("0 + 1 = 1", () => {
  console.log("밖 test");
  expect(fn.add(0, 1)).toBe(1); // 3
});

describe("Car 관련 작업", () => {
  beforeAll(() => console.log("안 beforeAll")); // 5
  beforeEach(() => console.log("안 beforeEach")); // 7
  afterEach(() => console.log("안 afterEach")); // 9
  afterAll(() => console.log("안 afterAll")); // 마지막 -1

  test("0 + 1 = 1", () => {
    console.log("안 test");
    expect(fn.add(0, 1)).toBe(1); // 8
  });
});

🔹 테스트 결과 및 흐름

(...생략)

밖 beforeAll
밖 beforeEach
밖 test
밖 afterEach
안 beforeAll
밖 beforeEach
안 beforeEach
안 test
안 afterEach
밖 afterEach
안 afterAll
밖 afterAll

 PASS  ./fn.test.js
  ✓ 0 + 1 = 1 (4 ms)
  Car 관련 작업
    ✓ 0 + 1 = 1 (2 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.136 s, estimated 1 s
Ran all test suites.
  1. describe
    1. 밖에 있는 beforeAll / afterAll 은 처음과 마지막에 실행.

    2. 밖에 있는 test 진행

      test 전 후로 밖에 있는 beforeEach / afterEach 실행

  2. describe 내부
    1. 밖에 있는 test 가 끝났으니까 내부로 진입

    2. 안에 있는 beforeAll / afterAll이 각각 처음과 끝에 실행

    3. 안에 있는 test 가 실행이 될 텐데, 이 test 전후로 beforeEachafterEach 가 실행이 되는데, ⭐️ 그 전에 밖에 beforeEach 가 먼저 실행이 됨

      끝나고 나서는 밖에 있는 afterEach 가 실행됨. ⭐️

📌 test.only(name, fn, timeout)

  • 사용하는 경우

    [단독 실행] .only 가 붙여진 test 케이스만 실행

    큰 테스트 파일을 디버깅할 때, 테스트의 하위 집합만 실행 하고 싶은 경우가 있을 것이다. .only를 사용하여 해당 테스트 파일에서 실행하고 싶은 테스트 지정 가능

  • 사용 후 제거

    디버깅에 사용하고 손상된 테스트를 수정한 후에는 제거

  • 별칭

    • it.only(name, fn, timeout)

    • fit(name, fn, timeout)

🔹 예시

외부의 어떤 요인 때문인지 or 코드 자체에 문제가 있는 건지 우선 파악하려는 경우

1. 문제 발생

  • fn.test.js
    const fn = require("./fn");
    
    let num = 0;
    test("0 더하기 1은 1", () => {
      expect(fn.add(num, 1)).toBe(1);
    });
    test("0 더하기 2은 2", () => {
      expect(fn.add(num, 2)).toBe(2);
    });
    test("0 더하기 3은 3", () => {
      expect(fn.add(num, 3)).toBe(3);
    });
    test("0 더하기 4은 4", () => {
      expect(fn.add(num, 4)).toBe(4);
      num = 10; // 👈 num의 값을 바꿔줌
    });
    test("0 더하기 5은 5", () => {
      expect(fn.add(num, 5)).toBe(6); // 👈 실패하도록 제작
    });
  • 테스트 결과

    마지막 테스트만 실패

2. 디버깅 (only 활용)

  • 이럴 때는 마지막 테스트만 한 번 더 실행해보는 것이 좋음.

    외부의 어떤 요인 때문인지 or 코드 자체의 문제인지 우선 파악

  • fn.test.js

    (...생략)
    
    test.only("0 더하기 5은 5", () => {  // 👈
      (...생략)
    });
  • 테스트 결과

    이 코드만 실행했는데도 실패 → 코드 자체에 문제 有

    • 해당 코드만 실행. 나머지 케이스들 모두 skip.

    • 불통 이유: 5여야 되는데 6 나옴

3. 문제의 코드 수정

  • fn.test.js
    (...생략)
    test.only("0 더하기 5은 5", () => {
      expect(fn.add(num, 5)).toBe(5); // 👈 5로 수정
    });
  • 수정 후 테스트 결과

    통과 → 이제 이 코드 자체에 문제 없다는 증거

4. only 제거 후, 재 테스트

  • 테스트 결과

    실패 → 실패한 test 케이스 코드에 대한 문제 없었기에 다른 외부 요인 찾아야 함.

5. 문제(외부 요인) 발견

  • fn.test.js

    const fn = require("./fn");
    
    let num = 0;
    
    (...생략)
    
    test("0 더하기 4은 4", () => {
      expect(fn.add(num, 4)).toBe(4);
      num = 10;   // 👈 [문제] num 값이 바뀜.
    });
    
    (...생략)

    이렇게 단순한 코드가 아니라, 당장 이 코드를 고치기 힘들다고 가정하면 우선 통과 시키기 위해서 이 부분을 건너뛰면 됨.

    • 방법: skip, 주석 처리

📌 test.skip(name, fn)

  • 사용하는 경우

    [제외] 건너뛸 테스트를 지정

    • 큰 코드 베이스를 유지 관리할 때 어떤 이유로 일시적으로 broken된 테스트를 발견할 수 있다. 이 테스트 실행을 건너뛰고 싶지만 이 코드를 삭제하고 싶지 않다면 test.skip을 사용하여 건너뛸 테스트 지정 가능

    • 당장 코드를 고치기 힘들 때, 우선 통과 시키기 위해 해당 부분을 건너뛰기

  • 주석 처리보다 나은 점

    들여쓰기 및 구문 강조 표시를 유지

  • 별칭

    • it.skip(name, fn)

    • xit(name, fn)

    • xtest(name, fn)

🔹 예시

test.only 예시 5 이후

  • skip 사용 (fn.test.js)
    (...생략)
    
    test.skip("0 더하기 4은 4", () => { // 👈 skip 사용
      (...생략)
    });
    
    (...생략)
  • 테스트 결과

    모두 성공 (skip 을 사용한 테스트는 건너뛰고 실행되어 제외됨)


참고

  • 코딩앙마 강좌_Jest
profile
복습 목적 블로그 입니다.

0개의 댓글