๐ ํ ์คํธ ๋๋ธ
์ผ๋จ ์์ฝํ์๋ฉด Mock, Stub, Spy ๊ฐ๊ฐ์ ํ ์คํธ์ ๋ชฉ์ ๊ณผ ์๊ตฌ ์ฌํญ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ์ฌ์ฉํ๊ณ , ์ข ์ข ํจ๊ป ์ฌ์ฉ๋๊ธฐ๋ ํ๋ค. ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ค ๋ณด๋ฉด ์ ์ธ ๊ฐ์ง ๊ฐ๋ ์ ๋ง์ฃผํ๊ฒ ๋๋๋ฐ ์ง๊ณ ๋์ด๊ฐ ๋ณด์.
Mock, Stub, Spy๋ ์ํํธ์จ์ด ํ ์คํธ์์ ์์ฃผ ์ฌ์ฉ๋๋ ์ฉ์ด๋ก, ๋ชจ๋ ํ ์คํธ ๋๋ธ(test double)์ ์ผ์ข ์ด๋ค.
ํ ์คํธ ๋๋ธ์ด๋, xUnit Test Patterns์ ์ ์์ธ ์ ๋ผ๋ ๋ฉ์ค์๋ก์ค(Gerard Meszaros)๊ฐ ๋ง๋ ์ฉ์ด๋ก ํ ์คํธ๋ฅผ ์งํํ๊ธฐ ์ด๋ ค์ด ๊ฒฝ์ฐ ์ด๋ฅผ ๋์ ํด ํ ์คํธ๋ฅผ ์งํํ ์ ์๋๋ก ๋ง๋ค์ด์ฃผ๋ ๊ฐ์ฒด๋ฅผ ๋งํ๋ค. (์ฐธ๊ณ : Test Double์ ์์๋ณด์)
๊ฐ๊ฐ์ ํ ์คํธ ์ค์ ์ธ๋ถ ์์คํ ์ด๋ ๋ณต์กํ ์ปดํฌ๋ํธ๋ฅผ ๋์ฒดํ๋ ๋ฐ์ ์ฌ์ฉ๋์ง๋ง, ๊ทธ ๋ชฉ์ ๊ณผ ์ฌ์ฉ ๋ฐฉ๋ฒ์ด ๋ค๋ฅด๋ค. ์๋์ ์ด๋ค์ ์ฐจ์ด์ ์ ์ ๋ฆฌํด ๋ณด์๋ค.
โ Mock
๊ธฐ๋ํ ๋๋ก ์ ํธ์ถ๋๋?
Mock์ ๋ฐํ๊ฐ์ด ์๋ ํจ์๋ฅผ ํ ์คํธํ ๋, ํน์ ๊ฐ์ฒด์์ ํน์ ํจ์๊ฐ ํธ์ถ๋์๋์ง ํ ์คํธํ ๋ ์ฌ์ฉํ๋ค. ๋ค์ ๋งํ๋ฉด, ๋ด๊ฐ ๋ฐ๋ผ๋ ํธ์ถ์ ๋ํ ๊ธฐ๋๋ฅผ ๋ช ์ํด ๋๊ณ , ๋ช ์ํ ๋ด์ฉ์ ๋ฐ๋ผ ์ ์๋ํ๋์ง ํ๋ก๊ทธ๋๋ฐ๋ ๊ฐ์ฒด์ธ ๊ฑฐ๋ค.
Mock์ ํ ์คํธ ์ค์ ํน์ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ ๊ฐ์ฒด๋ฅผ ๋์ฒดํ๋ ๋ฐ ์ฌ์ฉ๋๋ค. ์ฆ, ๊ฐ์ง ํ ์คํธ ์๋จ์ ๋ง๋ค์ด ๋๋ ๊ฑฐ๋ค. ์ด๋ ๊ฒ ๋์ฒด๋ Mock ๊ฐ์ฒด๋ ์์ ์ธ๊ธํ ๋๋ก ํน์ ๋ฉ์๋๊ฐ ํธ์ถ๋์๋์ง, ์ด๋ค ์ธ์๋ก ํธ์ถ๋์๋์ง ๊ฒ์ฌํ๋ ๋ฐ ์ค์ ์ ๋๋ค.
Mock์ ํ ์คํธ ๋์์ ์ธ๋ถ ์์กด์ฑ์ ๋์ฒดํ๊ณ , ๊ทธ ๊ฐ์ฒด์ ํ๋์ ์๋ฎฌ๋ ์ด์ ํ๋ค. ์๋ฅผ ๋ค์ด, ๋ฐ์ดํฐ ๋ฒ ์ด์ค๋ก๋ถํฐ ์กฐํํ ๊ฐ์ ์ฐ์ฐํ๋ ๋ก์ง์ ๊ตฌํํ๋ ์ํฉ์ด๋ผ๊ณ ํ์. ์ด ๋ก์ง์ ํ ์คํธํ๊ธฐ ์ํด์๋ ์ด์ฉ ์ ์์ด DB์ ์ํฅ์ ๋ฐ์ ๊ฒ์ด๋ฏ๋ก DB ์ํ์ ๋ฐ๋ผ ๋งค๋ฒ ๋ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ผ ์ ์๊ธฐ์ ํ ์คํธ๋ฅผ ํ๊ธฐ์๋ ๋ถ์์ ํ ์ํฉ์ด๋ค. ๋ถ์์ ํ๊ธฐ์ ์์ ์ ์ธ ๋์์ผ๋ก ๋์ฒดํ๋ ๊ฒ์ด๋ค.
์ฐ์ํ ํ ํฌ์ฝ์ค ํ๋ฆฌ์ฝ์ค ์ค, ์ซ์ ์ผ๊ตฌ ๋ฏธ์ ์ ํ ์คํธ ์ฝ๋ ์ผ๋ถ๋ฅผ ๋ณด๋ฉฐ mock์ ์ดํดํด ๋ณด์.
test('๊ฒ์ ์ข
๋ฃ ํ ์ฌ์์', async () => {
// ... ์๋ต
mockRandoms(randoms);
mockQuestions(answers);
// App ์ธ์คํด์ค ์์ฑ ๋ฐ play ํจ์ ์คํ
const app = new App();
await expect(app.play()).resolves.not.toThrow();
// ๋ก๊ทธ ์ถ๋ ฅ์ด ์์๋๋ก ์ด๋ฃจ์ด์ก๋์ง ๊ฒ์ฆ
messages.forEach((output) => {
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output));
});
});
์ ์์ ์ฝ๋๋ฅผ ๋ณด๋ฉด Mock ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ์ค์ ํจ์ ํธ์ถ์ ์ถ์ ํ๊ณ , ํด๋น ํจ์๊ฐ ํน์ ์กฐ๊ฑด์ ๋ง๊ฒ ํธ์ถ๋์๋์ง ๊ฒ์ฆํ๋ค. ๋๋ถ์ ํน์ ์ํฉ์์ ์ ์ ํ ๋ก๊ทธ๊ฐ ์ถ๋ ฅ๋๋์ง ํ์ธํ ์ ์๋ค.
โ Stub
๊ธฐ๋ํ ๊ฐ์ด ์ ๋ฐํ๋๋?
Stub์ ํน์ ๊ฐ์ฒด์์ ํน์ ํจ์๋ฅผ ํธ์ถํ ๋, ํน์ ํ ๊ฐ์ด ๋ฐํ๋๊ธฐ๋ฅผ ๊ธฐ๋ํ๋ฉฐ ๋๋ฏธ ๋ฐ์ดํฐ๋ฅผ ์ง์ ํ ๋ ์ฌ์ฉํ๋ค. 'stub'์ ์ฌ์ ์ ์๋ฏธ๋ฅผ ๋ฏธ๋ฃจ์ด ๋ณด์, ์ด๋ค ์ฐ๊บผ๊ธฐ ๊ฐ์ ๊ฒ์ ์ค๋นํด ๋์ด, ๊ธฐ๋ํ ๊ฐ์ ํ ์คํธํ๋ ๊ฒ์ด๋ผ๊ณ ์ดํดํ ์ ์์ ๊ฒ ๊ฐ๋ค.
๊ตฌ์ฒด์ ์ผ๋ก ํ์ด ๋งํ์๋ฉด, stub์ dummy ๊ฐ์ฒด๊ฐ ์ค์ ๋ก ๋์ํ๋ ๊ฒ์ฒ๋ผ ๊พธ๋ฉฐ ๋์ ๊ฐ์ฒด๋ก, ํ ์คํธ์์ ํธ์ถ๋ ์์ฒญ์ ๋ํด ๋ฏธ๋ฆฌ ์ค๋นํด ๋ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณตํ๋ค. ๋ฐ๋ผ์ ๋ด๊ฐ ๊ธฐ๋ํ ๊ฐ์ด ์ ๋ฐํ๋๋์ง ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ ์ ์๋ค.
Stub์ ํ ์คํธ ์ค์ ์ฌ์ฉ๋๋ ํ๋์ฝ๋ฉ๋ ๊ฐ ๋๋ ์๋ต์ ์ ๊ณตํ๋ ๋ฐ ์ฌ์ฉ๋๋ค. ๋ณต์กํ ๋ก์ง์ด๋ ์ธ๋ถ ์์คํ ๊ณผ์ ์ํธ์์ฉ์ ๋จ์ํํ๊ณ , ํ ์คํธ๋ฅผ ๋ ์์ธก ๊ฐ๋ฅํ๊ฒ ๋ง๋๋ ๋ฐ ์ค์ ์ ๋๋ฉฐ, ์ธํฐํ์ด์ค ๋๋ ๊ธฐ๋ณธ ํด๋์ค๊ฐ ์ต์ํ์ผ๋ก ๊ตฌํ๋ ์ํ๋ค.
์ฐ์ํ ํ ํฌ์ฝ์ค ํ๋ฆฌ์ฝ์ค ์ค, ์๋์ฐจ ๊ฒฝ์ฃผ ๋ฏธ์ ์ ํ ์คํธ ์ฝ๋ ์ผ๋ถ๋ฅผ ๋ณด๋ฉฐ stub์ ์ดํดํด ๋ณด์.
test.each([
[['pobi,javaji']],
[['pobi,eastjun']]
])('์ด๋ฆ์ ๋ํ ์์ธ ์ฒ๋ฆฌ', async (inputs) => {
// given: ์ง๋ฌธ ๋ชจ์๋ฅผ ์ค์
mockQuestions(inputs);
// when: App ์ธ์คํด์ค๋ฅผ ์์ฑ
const app = new App();
// then: App์ play ๋ฉ์๋๊ฐ ๋น๋๊ธฐ๋ก ์คํ๋ ๋ ์์ธ '[ERROR]'๋ฅผ ๋์ง๋์ง ํ์ธ
await expect(app.play()).rejects.toThrow('[ERROR]');
});
์ ์ฝ๋์์ [['pobi,javaji']], [['pobi,eastjun']]๋ ํ
์คํธ์์ ์ฌ์ฉํ๋ stub์ผ๋ก์, ์ค์ ์ฌ์ฉ์ ์
๋ ฅ์ ๋์ ํด ํ
์คํธ ํ๊ฒฝ์์ App ํด๋์ค์ ๋์์ ์๋ฎฌ๋ ์ด์
ํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.
์ฐธ๊ณ ๋ก test.each ๋ฉ์๋๋ ์ด๋ฌํ ์คํ
๋ค์ ๋ค๋ฃจ๋ ๋ฐ์ ๋น๋ฒํ๊ฒ ์ฌ์ฉ๋๋๋ฐ, ๊ฐ๊ฐ์ ๋ฐฐ์ด์ด ๋ค๋ฅธ ํ
์คํธ ์ผ์ด์ค๋ก ์ฌ์ฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ค์ํ ์
๋ ฅ ์๋๋ฆฌ์ค์ ๋ํ ๋์์ ๋ํ ์ฝ๋์ ๋ฐ๋ณต์ ์ค์ด๋ฉด์ ํจ์จ์ ์ผ๋ก ๊ฒ์ฆํ ์ ์๋ค.
โ Spy
ํจ์๊ฐ ์ ์ฌ์ฉ๋๊ณ ์๋?
Spy๋ ํน์ ํจ์๋ง ์ค์ ํจ์๋ฅผ ํธ์ถํ๊ฒ ํ๊ณ ์ถ์ ๋, ๊ธฐ์กด์ ๊ฐ์ฒด๋ ํจ์๋ฅผ ๊ฐ์ํ๋ ๋ฐ์ ์ฌ์ฉ๋๋ค. ์ด๋ ํจ์ ํธ์ถ, ์ ๋ฌ๋ ์ธ์, ๋ฐํ๊ฐ ๋ฑ์ ๊ธฐ๋กํ์ง๋ง, ์ค์ ๋ก์ง์ ๋์์ ๋ณ๊ฒฝํ์ง๋ ์๋๋ค.
ํ์ด ์ค๋ช ํ์๋ฉด, Spy๋ ์ฃผ๋ก ํ ์คํธ ์ค์ ํน์ ํจ์๋ ๋ฉ์๋๊ฐ ์ด๋ป๊ฒ ์ฌ์ฉ๋๋์ง ๊ด์ฐฐํ๊ณ ๊ธฐ๋กํ๋ ๋ฐ ์ฌ์ฉ๋๋ค. ๋ง์น ๊ฒ์์ ์ท์ ์ ์ ์คํ์ด๊ฐ ํน์ ํจ์๊ฐ ํธ์ถ๋์์ ๋ ํ์ธ์ด ํ์ํ ๋ถ๋ถ์ ๊ธฐ๋กํ๊ณ ์๋ ๋ชจ์ต์ ์์ํ๋ฉด ๋๋ค.
์๋ฅผ ๋ค์ด, ํจ์๊ฐ ๋ช ๋ฒ ํธ์ถ๋์๋์ง, ์ด๋ค ์ธ์๋ก ํธ์ถ๋์๋์ง ๋ฑ์ ๊ฒ์ฌํ ์ ์๋ค. ์ค์ ๊ตฌํ์ ์ ์งํ๋ฉด์๋ ํจ์์ ์ฌ์ฉ์ ๋ํ ์์ธํ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ ๊ฒ์ด ํน์ง์ด๋ค.
์ฐ์ํ ํ ํฌ์ฝ์ค ํ๋ฆฌ์ฝ์ค ์ซ์ ์ผ๊ตฌ ๋ฐ ์๋์ฐจ ๊ฒฝ์ฃผ ๋ฏธ์ ์ ๊ณตํต์ผ๋ก ๋ฑ์ฅํ๋ ํ ์คํธ ์ฝ๋ ์ผ๋ถ๋ฅผ ๋ณด๋ฉฐ spy๋ฅผ ์ดํดํด ๋ณด์.
// ๋ก๊ทธ ์ถ๋ ฅ์ ๊ฐ์ํ๋ Spy ํจ์๋ฅผ ์์ฑ
const getLogSpy = () => {
// MissionUtils์ Console.print๋ฅผ ๊ฐ์
const logSpy = jest.spyOn(MissionUtils.Console, 'print');
// ์ด์ ์ ๊ฐ์ ๊ธฐ๋ก์ ์ด๊ธฐํ
logSpy.mockClear();
// ์์ฑ๋ Spy ๊ฐ์ฒด๋ฅผ ๋ฐํ
return logSpy;
};
์ ์์ ์ฝ๋์์๋ jest.spyOn์ ์ฌ์ฉํ์ฌ MissionUtils.Console.print ํจ์์ ๋ํ spy๋ฅผ ์์ฑํ๋ฉฐ, ์ด spy๋ print ํจ์์ ์ค์ ํธ์ถ์ ๊ฐ์ํ๊ณ ๊ธฐ๋กํ๋ค. ํจ์๊ฐ ํธ์ถ๋์์ ๋์ ์ธ์, ํธ์ถ ํ์ ๋ฑ์ ์ถ์ ํ ์ ์๊ณ , ์์ ์ค๋ช
ํ ๊ฒ์ฒ๋ผ ์ค์ ํจ์์ ๋ก์ง์ ๊ทธ๋๋ก ์ ์ง๋๋ค.
๐ฉ๐ป๐ป ์์ฝํ์๋ฉด
· Mock์ ์ธ๋ถ ์์กด์ฑ์ ๋ชจ์ํ๊ณ , ํน์ ํ๋์ด ๋ฐ์ํ๋์ง๋ฅผ ๊ฒ์ฌํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.
· Stub์ ํ ์คํธ ์ค์ ํ์ํ ๋ฏธ๋ฆฌ ์ ํด์ง ์๋ต์ด๋ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.
· Spy๋ ์ค์ ๊ฐ์ฒด๋ฅผ ๊ฐ์ํ๋ฉด์ ํธ์ถ ์ ๋ณด๋ฅผ ๊ธฐ๋กํ์ง๋ง, ๊ฐ์ฒด์ ์ค์ ๋์์ ๋ณ๊ฒฝํ์ง ์๋๋ค.
์์ผ๋ก ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ ๋๋ ์์ ํ๋ ์์ํฌ๋ค์ด ์ ๊ณตํด ์ฃผ๋ ๊ธฐ๋ฅ์ ์ ์ฌ์ ์์ ์ฌ์ฉํ๋ฉฐ ์ ๋๋ก ๋ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด ๋ณด์. ์ด๋ค ์ํฉ์ ์ด๋ค ๊ฒ์ ์จ์ผ ํ๋์ง ํ์ตํ๊ณ ์ฌ์ฉํ๋ค๋ฉด ๋ ์ข์ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ง ์์๊น.
๐ ์ฐธ๊ณ ์๋ฃ
- Tecoble: Test Double์ ์์๋ณด์
๋๊ธ