My Dream Being Visualized

Jest를 활용한 TDD 단위 테스트 - 5편 본문

Backend/TDD

Jest를 활용한 TDD 단위 테스트 - 5편

마틴킴 2021. 12. 2. 12:30
728x90

 개인 공부를 위한 공간입니다. 틀린 부분 지적해주시면 감사하겠습니다 (_ _)

 

지난 시간에, Testing Asynchronous Code에 대해서 알아보았다.

 

이번 시간엔, Setup and Teardown이라는 주제로 넘어가보겠다.

https://jestjs.io/docs/setup-teardown

 

Setup and Teardown

테스트가 시작되기 전, 테스트가 다 끝나고 해야 할 작업들이 있을 수도 있다.

Jest는 이를 다루기 위한 헬퍼 함수들을 제공한다!

 

Repeating Setup For Many Tests

테스트마다 반복적으로 해야 될 작업이 있다면 beforeEachafterEach를 사용하자!

예를들어, 도시 정보를 담고 있는 데이터베이스를 여러번 접근해야 한다고 가정하자.

initializeCityDatabase()는 각 테스트 이전에 호출되어야 하며, clearCityDatabase()는 각 테스트 이후에 호출되어야 한다.

beforeEach(() => {
  initializeCityDatabase();
});

afterEach(() => {
  clearCityDatabase();
});

test('city database has Vienna', () => {
  expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
  expect(isCity('San Juan')).toBeTruthy();
});

지난 시간에 비동기 코드를 실행했던 것 처럼, 같은 방식으로 beforeEachafterEach 또한 비동기 코드를 실행할 수 있다.

return을 붙여, 동기 형태로 진행할 수 있게 기다리게 한다! initializeCitydatabase()가 promise resolve를 리턴하는 경우!

beforeEach(() => {
  return initializeCityDatabase();
});

 

One-Time Setup

최초 한번만 실행하고 싶을 때, setup 해야 할 코드들이 비동기라면, 약간 귀찮아 질 수 있다.

(아마, 함수를 따로 만들어서 호출하는 곳에서 동기화 시켜준다던지....)

Jest에서는 beforeAllafterAll을 지원한다.

예를들어, initialzeCityDatabase와 clearCityDatabase 둘 다 프로미스를 리턴한다면, ~CityDatabase는 테스트들 사이에서 재사용 될 수 있게 테스트 코드를 아래와 같이 바꿔 주어야 한다.

beforeAll(() => {
  return initializeCityDatabase();
});

afterAll(() => {
  return clearCityDatabase();
});

test('city database has Vienna', () => {
  expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
  expect(isCity('San Juan')).toBeTruthy();
});

 

Scoping

before와 after 블럭은 테스트 파일 내 모든 테스트에 적용되는데, describe 블럭을 사용하여 테스트를 그룹화 할 수 있다. 

describe 블락 내 넣은 before와 after 블럭은 오직 선언된 테스트 내 모든 파일이 적용 가능ㅌ하다.

예를들어, cityDatabase 뿐만 아니라 foodDatabase도 있다고 가정했을 때, 각 다른 테스트에 다른 setup을 해줄 수 있다.

// 전체 테스트 적용
beforeEach(() => {
  return initializeCityDatabase();
});

test('city database has Vienna', () => {
  expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
  expect(isCity('San Juan')).toBeTruthy();
});

describe('matching cities to foods', () => {
  // 현 describe 블락 내 설정
  beforeEach(() => {
    return initializeFoodDatabase();
  });

  test('Vienna <3 veal', () => {
    expect(isValidCityFoodPair('Vienna', 'Wiener Schnitzel')).toBe(true);
  });

  test('San Juan <3 plantains', () => {
    expect(isValidCityFoodPair('San Juan', 'Mofongo')).toBe(true);
  });
});

당연히, 맨 위에 있는 beforeEach는 describe 블럭 안에 있는 beforeEach보다 먼저 실행된다. 

// 조금 헷갈리지만, 천천히 뜯어보면 동작하는 원리를 이해할 수 있다!
beforeAll(() => console.log('1 - beforeAll')); 		-- 1
afterAll(() => console.log('1 - afterAll')); 		-- 12
beforeEach(() => console.log('1 - beforeEach')); 	-- 2, 6
afterEach(() => console.log('1 - afterEach')); 		-- 4, 10
test('', () => console.log('1 - test')); 		-- 3
describe('Scoped / Nested block', () => {
  beforeAll(() => console.log('2 - beforeAll')); 	-- 5
  afterAll(() => console.log('2 - afterAll')); 		-- 11
  beforeEach(() => console.log('2 - beforeEach')); 	-- 7
  afterEach(() => console.log('2 - afterEach')); 	-- 9
  test('', () => console.log('2 - test')); 		-- 8
});

 

Order of execution of describe and test blocks

Jest는 테스트가 진행되기 전에 테스트 파일 내 모든 describe를 실행한다. 

describe 블락 내부가 아닌 beforeafter 핸들러 내부에 setup and teardown을 하는 이유이기도 하다.

describe 블락이 끝났을 때, Jest는 각각 테스트가 끝나고 다음 테스트로 넘어가길 기다리면서 test list 내에 정의된 순서대로 모든 테스트를 실행한다.

describe('outer', () => {
  console.log('describe outer-a');			-- 1

  describe('describe inner 1', () => {
    console.log('describe inner 1');			-- 2
    test('test 1', () => {
      console.log('test for describe inner 1');		-- 6
      expect(true).toEqual(true);
    });
  });

  console.log('describe outer-b');			-- 3

  test('test 1', () => {
    console.log('test for describe outer');		-- 7
    expect(true).toEqual(true);
  });

  describe('describe inner 2', () => {
    console.log('describe inner 2');			-- 4
    test('test for describe inner 2', () => {
      console.log('test for describe inner 2');		-- 8
      expect(false).toEqual(false);
    });
  });

  console.log('describe outer-c');			-- 5
});

 

General Advice

테스트 규모가 클 때, 특정 테스트가 실패한다면, beforeEach 상태 값 확인을 통해 서로 의존하고 있는지(interfering) 확인해 볼 필요가 있다.

 

 

테스트 코드를 작성할 때에도, 셋팅 및 구조와 관련하여 선행되어야 할 작업들에 대해서 알아보았다.
다음 시간에는, TDD 단위 테스트에서 중요한 "Mock Functions"에 대해서 알아보겠다.
재밌었다!