테스트 전후 작업
2022-05-18
테스트 전 후에 할 수 있는 작업들에 대해서 한번 알아 보겠습니다.
테스트를 작성하다보면 테스트 전후의 해줘야 될 작업들이 생깁니다.
Jest는 그런 처리를 할 수 있도록 헬퍼 함수를 제공합니다.
우선 예제를 이렇게 작성해 볼게요.
fn.test.js
const fn = require("./fn");
let num = 0;
test("0 더하기 1은 1이야", () => {
num = fn.add(num, 1);
expect(num).toBe(1);
});
PASS
v 0 더하기 1은 1이야 (2ms)
이런 테스트를 여러개 진행해 보겠습니다.
fn.test.js
const fn = require("./fn");
let num = 0;
test("0 더하기 1은 1이야", () => {
num = fn.add(num, 1);
expect(num).toBe(1);
});
test("0 더하기 2은 2이야", () => {
num = fn.add(num, 2);
expect(num).toBe(2);
});
test("0 더하기 3은 3이야", () => {
num = fn.add(num, 3);
expect(num).toBe(3);
});
test("0 더하기 4은 4이야", () => {
num = fn.add(num, 4);
expect(num).toBe(4);
});
FAIL
v 0 더하기 1은 1이야 (2ms)
x 0 더하기 2은 2이야 (2ms)
x 0 더하기 3은 3이야
x 0 더하기 4은 4이야
Expected: 2
Received: 3
Expected: 3
Received: 6
Expected: 4
Received: 10
로그를 살펴 보면, 문제점이 보이시죠?
num에 새로운 값이 계속 할당 되고 있어서 그렇습니다.
이런 테스트를 모두 통과 시키려면, 각 테스트를 실행하기 직전에 num을 초기화 해주는 과정이 필요합니다.
beforeEach
이럴때 쓸수 있는 것이 beforeEach 입니다.
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("0 더하기 2은 2이야", () => {
num = fn.add(num, 2);
expect(num).toBe(2);
});
test("0 더하기 3은 3이야", () => {
num = fn.add(num, 3);
expect(num).toBe(3);
});
test("0 더하기 4은 4이야", () => {
num = fn.add(num, 4);
expect(num).toBe(4);
});
PASS
v 0 더하기 1은 1이야 (2ms)
v 0 더하기 2은 2이야 (2ms)
v 0 더하기 3은 3이야
v 0 더하기 4은 4이야
모두 통과합니다.
초기값을 10으로 변경하더라도 상관 없을 것입니다.
let num = 10;
테스트를 실행하기 전에 0으로 할당을 하기 때문입니다.
afterEach
afterEach는 테스트 직후에 실행됩니다.
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("0 더하기 2은 2이야", () => {
num = fn.add(num, 2);
expect(num).toBe(2);
});
test("0 더하기 3은 3이야", () => {
num = fn.add(num, 3);
expect(num).toBe(3);
});
test("0 더하기 4은 4이야", () => {
num = fn.add(num, 4);
expect(num).toBe(4);
});
FAIL
x 0 더하기 1은 1이야 (3ms)
v 0 더하기 2은 2이야
v 0 더하기 3은 3이야
v 0 더하기 4은 4이야 (1ms)
이번에는 첫번쨰 케이스가 통과를 못하죠.
초기값이 10이기 때문입니다.
0으로 바뀌는 타이밍은 테스트 이후에 바뀌기 때문에 첫번째 케이스는 통과를 하지 못했습니다.
그런데 만약에 전후 작업이 시간이 조금 걸리는 작업이라면 어떻게 될까요?
예를 들어 테스트 이전에 db접속하여 유저 정보를 가지고 오고, 테스트 이후에 db커넥션을 끊는 작업을 더 만들어 보도록 하겠습니다.
각 작업은 약 0.5초 걸린다고 가정해 보겠습니다.
fn.js
const fn = {
add: (num1, num2) => num1 + num2,
connectUserDb : () => {
return new Promise(res => {
setTimeout(() => {
res({
name: "Mike",
age: 30,
gender: "male",
});
}, 500);
});
},
disconnectDb: () => {
return new Promise(res => {
setTimeout(() => {
res();
}, 500);
});
}
};
module.exports = fn;
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");
});
PASS
v 이름은 Mike (1006 ms)
테스트를 해보면 1초의 시간이 걸린것을 알 수 있습니다.
몇 가지를 더 만들어 보겠습니다.
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");
});
PASS
v 이름은 Mike (1005 ms)
v 나이는 30 (1006 ms)
v 성별은 남성 (1004 ms)
Time: 3.893 s
약 4초 정도 걸렸어요,
왜냐하면 이 테스트의 전 후에 각각 0.5초가 소유 되기 때문이죠.
그래서 테스트 한번 하는 데 1초가 추가적으로 걸리는 겁니다.
이런식으로 테스트 케이스가 점점 더 들어가면 안되겠죠.
사실 db는 한번만 연결해서 유저정보를 가지고 오고, 모든 테스트를 마친 후에 끊어도 상관없으니까, 최초에 한번 최후에 한번 하는게 더 좋습니다.
beforeAll, afterAll
fn.test.js
const fn = require("./fn");
let user;
beforeAll(async() => {
user = await fn.connectUserDb();
});
afterAll(() => {
return fn.disconnectDb();
});
test("이름은 Mike", () => {
expect(user.name).toBe("Mike");
});
test("나이는 30", () => {
expect(user.age).toBe(30);
});
test("성별은 남성", () => {
expect(user.gender).toBe("male");
});
PASS
v 이름은 Mike (3 ms)
v 나이는 30
v 성별은 남성
Time: 1.901 s
1.9초 정도 걸렸죠. 아까전 보다 훨씬 짧아졌습니다.
전체 테스트 전후로 1초만 추가적으로 추가된 것을 알 수 있습니다.
테스트하는 디비가 늘어난다면, 어떻게 될까요?
유저정보가 아니라 자동차 정보도 가지고 온다면 어떻게 될까요?
fn.js
const fn = {
add: (num1, num2) => num1 + num2,
connectUserDb : () => {
return new Promise(res => {
setTimeout(() => {
res({
name: "Mike",
age: 30,
gender: "male",
});
}, 500);
});
},
disconnectDb: () => {
return new Promise(res => {
setTimeout(() => {
res();
}, 500);
});
},
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;
위와 같이 car db에 대한 것을 작성합니다.
비슷한 것 끼리 묶을 수 있습니다.
fn.test.js
const fn = require("./fn");
let user;
beforeAll(async() => {
user = await fn.connectUserDb();
});
afterAll(() => {
return fn.disconnectDb();
});
test("이름은 Mike", () => {
expect(user.name).toBe("Mike");
});
test("나이는 30", () => {
expect(user.age).toBe(30);
});
test("성별은 남성", () => {
expect(user.gender).toBe("male");
});
describe("Car 관련 작업", () => {
let car;
beforeAll(async() => {
user = await fn.connectUserDb();
});
afterAll(() => {
return fn.disconnectDb();
});
test("이름은 z4", () => {
expect(user.name).toBe("z4");
});
test("브랜드는 bmw", () => {
expect(user.brand).toBe(30);
});
test("색상은 red", () => {
expect(user.color).toBe("male");
});
});
PASS
v 이름은 Mike (3 ms)
v 나이는 30
v 성별은 남성
Car 관 련 작 업
v 이름은 z4 (5 ms)
v 브랜드는 bmw (1 ms)
v 색상은 red
Time: 2.901 s
자동차 관련작업은 describe 로 넣었구요.
describe 안의 befor after는 이 안에서만 동작하게 됩니다.
실행해 보면 설명과 뎁스로 구분이 됩니다.
실행 순서
describe 내부와 외부로 나눠서 테스트를 해보겠습니다.
안에 있는 beforeEach와 afterEach가 실행 되기전이랑 되기후에는 밖에 있는 beforeEach와 afterEach가 실행되게 됩니다.
이 순서를 기억해두셔야 됩니다.
fn.test.js
const fn = require("./fn");
beforeAll(() => console.log("밖 beforeAll")); // 1
beforeEach(() => console.log("밖 beforeEach")); // 2, 7
afterEach(() => console.log("밖 afterEach")); // 4, 11
afterAll(() => console.log("밖 afterAll")); // 5
test("0 + 1 = 1", () => {
console.log("밖 test");
expect(fn.add(0, 1)).toBe(1); // 3
});
describe("Car 관련 작업", () => {
beforeAll(() => console.log("안 beforeAll")); // 6
beforeEach(() => console.log("안 beforeEach")); // 8
afterEach(() => console.log("안 afterEach")); // 10
afterAll(() => console.log("안 afterAll")); // 12
test("0 + 1 = 1", () => {
console.log("안 test");
expect(fn.add(0, 1)).toBe(1); // 9
});
});
PASS
console.log
밖 beforeAll
밖 beforeEach
밖 test
밖 afterEach
안 beforeAll
밖 beforeEach
안 beforeEach
안 test
안 afterEach
밖 afterEach
안 afterAll
밖 afterAll
only
또 다른 예제를 만들어서 몇 가지 더 테스트를 해 볼게요.
fn.test.js
const fn = require("./fn");
let num = 0;
test("0 + 1 = 1", () => {
expect(fn.add(0, 1)).toBe(1);
});
test("0 + 2 = 2", () => {
expect(fn.add(0, 2)).toBe(2);
});
test("0 + 3 = 3", () => {
expect(fn.add(0, 3)).toBe(3);
});
test("0 + 4 = 4", () => {
expect(fn.add(0, 4)).toBe(4);
num = 10;
});
test("0 + 5 = 5", () => {
expect(fn.add(0, 5)).toBe(6);
});
FAIL
v 0 + 1 = 1
v 0 + 2 = 2
v 0 + 3 = 3
v 0 + 4 = 4
x 0 + 5 = 5
Expected: 6
Received: 15
마지막 테스트만 실패 했습니다.
이런경우 마지막 테스트만 실행해보는 것이 좋습니다.
이유는 외부의 어떤 요인인지, 아니면 이 코드 자체에 문제가 있는건지, 파악을 하기 위해서 입니다.
이럴떄에는 테스트 뒤에 only를 붙여 주시면 됩니다.
fn.test.js
const fn = require("./fn");
let num = 0;
test("0 + 1 = 1", () => {
expect(fn.add(0, 1)).toBe(1);
});
test("0 + 2 = 2", () => {
expect(fn.add(0, 2)).toBe(2);
});
test("0 + 3 = 3", () => {
expect(fn.add(0, 3)).toBe(3);
});
test("0 + 4 = 4", () => {
expect(fn.add(0, 4)).toBe(4);
num = 10;
});
test.only("0 + 5 = 5", () => {
expect(fn.add(0, 5)).toBe(6);
});
FAIL
x 0 + 5 = 5
o skipped 0 + 1 = 1
o skipped 0 + 2 = 2
o skipped 0 + 3 = 3
o skipped 0 + 4 = 4
Expected: 6
Received: 5
나머지는 스킵이 되고, 마지막 코드만 실행 됐는데, 그래도 실패한 것을 보면, 코드 자체에 문제가 있다는 것을 파악할 수 있습니다.
5여야 되는데, 6이라고 되어 있기 때문에 그렇습니다.
fn.test.js
const fn = require("./fn");
let num = 0;
test("0 + 1 = 1", () => {
expect(fn.add(0, 1)).toBe(1);
});
test("0 + 2 = 2", () => {
expect(fn.add(0, 2)).toBe(2);
});
test("0 + 3 = 3", () => {
expect(fn.add(0, 3)).toBe(3);
});
test("0 + 4 = 4", () => {
expect(fn.add(0, 4)).toBe(4);
num = 10;
});
test.only("0 + 5 = 5", () => {
expect(fn.add(0, 5)).toBe(5);
});
PASS
v 0 + 5 = 5
o skipped 0 + 1 = 1
o skipped 0 + 2 = 2
o skipped 0 + 3 = 3
o skipped 0 + 4 = 4
5로 바꾸니 통과를 하였습니다.
그럼 only를 지우고 테스트 해보겠습니다.
fn.test.js
const fn = require("./fn");
let num = 0;
test("0 + 1 = 1", () => {
expect(fn.add(0, 1)).toBe(1);
});
test("0 + 2 = 2", () => {
expect(fn.add(0, 2)).toBe(2);
});
test("0 + 3 = 3", () => {
expect(fn.add(0, 3)).toBe(3);
});
test("0 + 4 = 4", () => {
expect(fn.add(0, 4)).toBe(4);
num = 10;
});
test("0 + 5 = 5", () => {
expect(fn.add(0, 5)).toBe(5);
});
FAIL
v 0 + 1 = 1
v 0 + 2 = 2
v 0 + 3 = 3
v 0 + 4 = 4
x 0 + 5 = 5
Expected: 5
Received: 15
그래도 실패한 것을 알 수 있습니다.
마지막 코드 자체에는 문제가 없었기 때문에, 다른 외부 요인이 없는지 알아봐야 합니다.
num = 10
위의 코드가 문제 일 것입니다.
skip
이렇게 단순한 코드라면 금방 고치겠지만, 단순한 코드가 아니라면 어떻게 해야 할까요?
우선 건너띄기 위해서 주석처리를 해도 되겠지만, 스킵을 사용해 보겠습니다.
fn.test.js
const fn = require("./fn");
let num = 0;
test("0 + 1 = 1", () => {
expect(fn.add(0, 1)).toBe(1);
});
test("0 + 2 = 2", () => {
expect(fn.add(0, 2)).toBe(2);
});
test("0 + 3 = 3", () => {
expect(fn.add(0, 3)).toBe(3);
});
test.skip("0 + 4 = 4", () => {
expect(fn.add(0, 4)).toBe(4);
num = 10;
});
test("0 + 5 = 5", () => {
expect(fn.add(0, 5)).toBe(5);
});
PASS
v 0 + 1 = 1
v 0 + 2 = 2
v 0 + 3 = 3
v 0 + 5 = 5
o skipped 0 + 4 = 4
스킵을 하니 해당 부분을 건너띄고 실행이 됩니다.
마무리
테스트 전후 작업 only을 통한 단독실행 skip을 통한 제외에 대해서 알아 보았습니다.