๐Ÿงฑ Jest ๊ฐœ๋… ์ •๋ฆฌ - ํŒŒ๋ผ๋ฏธํ„ฐํ™” ํ…Œ์ŠคํŠธ(test.each() & describe.each())

JS Kยท2025๋…„ 10์›” 22์ผ
post-thumbnail

์›๋ฌธ ์ฐธ๊ณ : DaleSeo ๋ธ”๋กœ๊ทธ - Jest๋กœ ํŒŒ๋ผ๋ฏธํ„ฐํ™” ํ…Œ์ŠคํŠธํ•˜๊ธฐ


๐ŸŽฏ ๊ฐœ์š”

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋‹ค ๋ณด๋ฉด ์ž…๋ ฅ๊ฐ’๋งŒ ๋‹ค๋ฅธ ๋™์ผํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ๋ฐ˜๋ณต๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.
์ด๋•Œ Jest์˜ test.each()์™€ describe.each()๋ฅผ ์ด์šฉํ•˜๋ฉด ๋ฐ˜๋ณต๋˜๋Š” ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ ,
๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜(๋ฐ์ดํ„ฐ ๋“œ๋ฆฌ๋ธ) ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.





1๏ธโƒฃ test.each โ€” ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋กœ ๋ฐ˜๋ณต

๐Ÿ“˜ ์˜ˆ์‹œ: ๋ง์…ˆ ํ•จ์ˆ˜

// add.js
export const add = (a, b) => a + b;

// add.test.js
import { add } from './add';

test.each([
  [1, 2, 3],
  [2, 2, 4],
  [10, -5, 5],
])('add(%i, %i) = %i', (a, b, expected) => {
  expect(add(a, b)).toBe(expected);
});

โœ… ํŠน์ง•

  • 2์ฐจ์› ๋ฐฐ์—ด๋กœ ์ž…๋ ฅ๊ฐ’์„ ๋‚˜์—ดํ•ด ํ•œ ๋ฒˆ์— ์—ฌ๋Ÿฌ ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰
  • %i, %s, %p ๋“ฑ์˜ ํฌ๋งท ๋ฌธ์ž์—ด์„ ํ†ตํ•ด ํ…Œ์ŠคํŠธ ์ด๋ฆ„์— ๋ฐ์ดํ„ฐ ์‚ฝ์ž… ๊ฐ€๋Šฅ
  • it.each() ๋„ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ (test์˜ alias)

๐Ÿ“ˆ ์ถœ๋ ฅ ์˜ˆ์‹œ

โœ“ add(1, 2) = 3
โœ“ add(2, 2) = 4
โœ“ add(10, -5) = 5

2๏ธโƒฃ describe.each โ€” ์—ฌ๋Ÿฌ ํ…Œ์ŠคํŠธ(it)๋ฅผ ๋ฐ์ดํ„ฐ ์„ธํŠธ๋ณ„๋กœ ๋ฐ˜๋ณต

๐Ÿ“˜ ์˜ˆ์‹œ: ์ธ์‚ฌ๋ง ์ƒ์„ฑ ํ•จ์ˆ˜

// greet.js
export function greet(name, { upper = false, prefix = '' } = {}) {
  let msg = `${prefix}${name}`;
  return upper ? msg.toUpperCase() : msg;
}

// greet.test.js
import { greet } from './greet';

describe.each([
  ['๊ธฐ๋ณธ',           { upper: false, prefix: '' }],
  ['๋Œ€๋ฌธ์ž ์˜ต์…˜',     { upper: true,  prefix: '' }],
  ['์ ‘๋‘์‚ฌ ์˜ต์…˜',     { upper: false, prefix: 'Mr. ' }],
])('greet ์˜ต์…˜: %s', (_label, options) => {
  it('์ด๋ฆ„์„ ํฌํ•จํ•ด์•ผ ํ•œ๋‹ค', () => {
    const out = greet('kim', options);
    expect(out).toEqual(expect.stringContaining('kim'));
  });

  it('๋Œ€๋ฌธ์ž ์˜ต์…˜์„ ๋ฐ˜์˜ํ•œ๋‹ค', () => {
    const out = greet('kim', options);
    const isUpper = out === out.toUpperCase();
    expect(isUpper).toBe(Boolean(options.upper));
  });

  it('์ ‘๋‘์‚ฌ ์˜ต์…˜์„ ๋ฐ˜์˜ํ•œ๋‹ค', () => {
    const out = greet('kim', options);
    expect(out).toEqual(expect.stringContaining(options.prefix));
  });
});

โœ… ํŠน์ง•

  • ํ…Œ์ŠคํŠธ ๊ทธ๋ฃน ์ „์ฒด(describe ๋ธ”๋ก) ๋ฅผ ๋ฐ์ดํ„ฐ๋ณ„๋กœ ๋ฐ˜๋ณต ์‹คํ–‰
  • ๋™์ผํ•œ ๋กœ์ง์„ ํ™˜๊ฒฝ/์˜ต์…˜/์„ค์ • ์„ธํŠธ๋งˆ๋‹ค ๋ฐ˜๋ณต ๊ฒ€์ฆํ•  ๋•Œ ์œ ์šฉ

๐Ÿ“ˆ ์ถœ๋ ฅ ์˜ˆ์‹œ

 PASS  ./greet.test.js
  greet ์˜ต์…˜: ๊ธฐ๋ณธ
    โœ“ ์ด๋ฆ„์„ ํฌํ•จํ•ด์•ผ ํ•œ๋‹ค
    โœ“ ๋Œ€๋ฌธ์ž ์˜ต์…˜์„ ๋ฐ˜์˜ํ•œ๋‹ค
    โœ“ ์ ‘๋‘์‚ฌ ์˜ต์…˜์„ ๋ฐ˜์˜ํ•œ๋‹ค
  greet ์˜ต์…˜: ๋Œ€๋ฌธ์ž ์˜ต์…˜
    โœ“ ์ด๋ฆ„์„ ํฌํ•จํ•ด์•ผ ํ•œ๋‹ค
    โœ“ ๋Œ€๋ฌธ์ž ์˜ต์…˜์„ ๋ฐ˜์˜ํ•œ๋‹ค
    โœ“ ์ ‘๋‘์‚ฌ ์˜ต์…˜์„ ๋ฐ˜์˜ํ•œ๋‹ค
  greet ์˜ต์…˜: ์ ‘๋‘์‚ฌ ์˜ต์…˜
    โœ“ ์ด๋ฆ„์„ ํฌํ•จํ•ด์•ผ ํ•œ๋‹ค
    โœ“ ๋Œ€๋ฌธ์ž ์˜ต์…˜์„ ๋ฐ˜์˜ํ•œ๋‹ค
    โœ“ ์ ‘๋‘์‚ฌ ์˜ต์…˜์„ ๋ฐ˜์˜ํ•œ๋‹ค





3๏ธโƒฃ ํ…œํ”Œ๋ฆฟ ๋ฆฌํ„ฐ๋Ÿด ํ‘œ๊ธฐ๋ฒ•

Jest๋Š” ๋ฐฐ์—ด ์™ธ์—๋„ ํ‘œ ํ˜•ํƒœ์˜ ๋ฐ์ดํ„ฐ ์ž…๋ ฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

test.each`
  a    | b    | expected
  ${1} | ${2} | ${3}
  ${2} | ${2} | ${4}
  ${5} | ${-2}| ${3}
`('add($a, $b) = $expected', ({ a, b, expected }) => {
  expect(add(a, b)).toBe(expected);
});

๊ฐ€๋…์„ฑ์ด ๋†’๊ณ , ํ…Œ์ด๋ธ” ํ˜•ํƒœ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•œ๋ˆˆ์— ๋“ค์–ด์˜ต๋‹ˆ๋‹ค.





4๏ธโƒฃ test.each vs describe.each ๋น„๊ต

๊ตฌ๋ถ„๋ฐ˜๋ณต ๋‹จ์œ„์šฉ๋„์žฅ์ ์˜ˆ์‹œ
test.each๊ฐœ๋ณ„ ํ…Œ์ŠคํŠธํ•œ ํ•จ์ˆ˜์˜ ์ž…๋ ฅ๊ฐ’ ๋ฐ˜๋ณต๋‹จ์ˆœํ•˜๊ณ  ๋น ๋ฆ„add(a,b)
describe.eachํ…Œ์ŠคํŠธ ๊ทธ๋ฃน์—ฌ๋Ÿฌ ํ…Œ์ŠคํŠธ๋ฅผ ํ™˜๊ฒฝ๋ณ„๋กœ ๋ฐ˜๋ณตbefore/after ํ›… ์‚ฌ์šฉ ๊ฐ€๋Šฅ์˜ต์…˜, DB ํ™˜๊ฒฝ๋ณ„ ํ…Œ์ŠคํŠธ

์„ ํƒ ๊ฐ€์ด๋“œ

  • โ€œ๊ฐ™์€ ๋™์ž‘์„ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋กœ ๊ฒ€์ฆโ€ โ†’ test.each
  • โ€œํ™˜๊ฒฝ/์˜ต์…˜์— ๋”ฐ๋ผ ๋™์ž‘์ด ๋‹ฌ๋ผ์ง€๋Š” ๊ทธ๋ฃน ํ…Œ์ŠคํŠธโ€ โ†’ describe.each





5๏ธโƒฃ describe.each์˜ before/after ํ›… ์‚ฌ์šฉ

describe.each๋Š” ๋ฐ์ดํ„ฐ ์„ธํŠธ๋งˆ๋‹ค ๋…๋ฆฝ๋œ describe ๋ธ”๋ก์„ ์ƒ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์—,
๊ฐ ๋ธ”๋ก ๋‚ด์—์„œ beforeAll, afterAll, beforeEach, afterEach ํ›…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“˜ ์˜ˆ์‹œ: DB ํ™˜๊ฒฝ๋ณ„ ํ…Œ์ŠคํŠธ

describe.each([
  ['MySQL', { host: 'localhost', port: 3306 }],
  ['PostgreSQL', { host: 'localhost', port: 5432 }],
])('DB ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ: %s', (dbName, config) => {
  beforeAll(() => {
    console.log(`${dbName} ์—ฐ๊ฒฐ ์‹œ์ž‘`);
    // connectToDB(config)
  });

  afterAll(() => {
    console.log(`${dbName} ์—ฐ๊ฒฐ ํ•ด์ œ`);
    // disconnectFromDB()
  });

  test('์—ฐ๊ฒฐ ์„ฑ๊ณต ์—ฌ๋ถ€ ํ™•์ธ', () => {
    expect(true).toBe(true); // ์‹ค์ œ๋ก  DB ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ
  });

  test('์ฟผ๋ฆฌ ์‹คํ–‰ ํ…Œ์ŠคํŠธ', () => {
    expect(true).toBe(true);
  });
});

๐Ÿ’ก ์‹คํ–‰ ํ๋ฆ„

  1. ์ฒซ ๋ฒˆ์งธ describe(MySQL)
    โ†’ beforeAll โ†’ 2๊ฐœ์˜ ํ…Œ์ŠคํŠธ โ†’ afterAll
  2. ๋‘ ๋ฒˆ์งธ describe(PostgreSQL)
    โ†’ beforeAll โ†’ 2๊ฐœ์˜ ํ…Œ์ŠคํŠธ โ†’ afterAll

์ฆ‰, ๊ฐ ๋ฐ์ดํ„ฐ ์„ธํŠธ๋งˆ๋‹ค setup(์ค€๋น„)๊ณผ cleanup(์ •๋ฆฌ)๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค.





6๏ธโƒฃ ํ•ต์‹ฌ ์š”์•ฝ

  • test.each: ๋™์ผ ํ…Œ์ŠคํŠธ๋ฅผ ์—ฌ๋Ÿฌ ์ž…๋ ฅ๊ฐ’์œผ๋กœ ๋ฐ˜๋ณต
  • describe.each: ์—ฌ๋Ÿฌ ํ…Œ์ŠคํŠธ ๊ทธ๋ฃน์„ ํ™˜๊ฒฝ๋ณ„๋กœ ๋ฐ˜๋ณต
  • describe.each๋Š” ๊ฐ ๊ทธ๋ฃน๋งˆ๋‹ค before/after ํ›… ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • ์œ ์ง€๋ณด์ˆ˜์„ฑ๊ณผ ๊ฐ€๋…์„ฑ์„ ๋†’์ด๋Š” ๋ฐ์ดํ„ฐ ์ค‘์‹ฌ ํ…Œ์ŠคํŠธ ํŒจํ„ด
  • ํ˜„์‹ค์ ์ธ ์‚ฌ์šฉ ์˜ˆ์‹œ:
    • ์ˆ˜ํ•™ ํ•จ์ˆ˜ ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ (test.each)
    • ์˜ต์…˜/ํ™˜๊ฒฝ๋ณ„ ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ (describe.each)
    • ๋‹ค๊ตญ์–ดยท๋‹คํ…Œ๋งˆ UI ํ…Œ์ŠคํŠธ (describe.each)

profile
1.01^365

0๊ฐœ์˜ ๋Œ“๊ธ€