tomatopickles404 ๋‹˜์˜ ์ƒ์„ธํŽ˜์ด์ง€ ๏ผž [9ํŒ€ ๊ถŒ์ง€ํ˜ธ] Chapter ๐Ÿท 3-1. ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๐Ÿงฆ

HARD

7์ฃผ์ฐจ ๊ณผ์ œ ์ฒดํฌํฌ์ธํŠธ

๊ธฐ๋ณธ๊ณผ์ œ

  • ์ด 11๊ฐœ์˜ ํŒŒ์ผ, 115๊ฐœ์˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ๋ฌด์‚ฌํžˆ ์ž‘์„ฑํ•˜๊ณ  ํ†ต๊ณผ์‹œํ‚จ๋‹ค.

์งˆ๋ฌธ

Q. handlersUtils์— ๋‚จ๊ธด ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•ด์ฃผ์„ธ์š”.

์šฉ๋„์— ๋”ฐ๋ผ ๊ฐœ๋ณ„์ ์œผ๋กœ ๋ถ„๋ฆฌ๋˜์–ด์žˆ๋˜ ํ•จ์ˆ˜๋ฅผ createMockHandlers๋ผ๋Š” ํ•˜๋‚˜์˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋กœ ํ†ตํ•ฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜๋Š” ํด๋กœ์ €(Closure)๋ฅผ ํ™œ์šฉํ•˜์—ฌ, ๊ฐ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ณ ์œ ํ•œ ์ƒํƒœ๋ฅผ ๊ฐ–๋Š” ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

export const createMockHandlers = (initEvents = [] as Event[]) => {
  let testEvents = [...initEvents];

  const handlers = [
    http.get('/api/events', () => {
      return HttpResponse.json({ events: testEvents });
    }),

    http.post('/api/events', async ({ request }) => {
      const event = (await request.json()) as Event;
      const newEvent = { ...event, id: crypto.randomUUID() };
      testEvents.push(newEvent);
      return HttpResponse.json(newEvent);
    }),

    http.put('/api/events/:id', async ({ request, params }) => {
      const event = (await request.json()) as Event;
      const { id } = params;
      testEvents = testEvents.map((e) => (e.id === id ? event : e));
      return HttpResponse.json(event);
    }),

    http.delete('/api/events/:id', async ({ params }) => {
      const { id } = params;
      testEvents = testEvents.filter((event) => event.id !== id);
      return HttpResponse.json(id);
    }),
  ];

  const getEvents = () => {
    return testEvents;
  };

  const reset = () => {
    testEvents = [...initEvents];
  };

  return { handlers, reset, getEvents };
};

[์‚ฌ์šฉ ์˜ˆ์‹œ]

  it("๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜ ์‹œ '์ผ์ • ์‚ญ์ œ ์‹คํŒจ'๋ผ๋Š” ํ…์ŠคํŠธ๊ฐ€ ๋…ธ์ถœ๋˜๋ฉฐ ์ด๋ฒคํŠธ ์‚ญ์ œ๊ฐ€ ์‹คํŒจํ•ด์•ผ ํ•œ๋‹ค", async () => {
    const { handlers } = createMockHandlers(initialEventData);
    server.use(...handlers);
   ...

[์ฃผ์š” ๊ตฌํ˜„]

  • createMockHandlers(initialState): ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„, ํ•ด๋‹น ์ƒํƒœ์—๋งŒ ์ ‘๊ทผํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ ๋ฐฐ์—ด๊ณผ ์ƒํƒœ ์ œ์–ด ํ•จ์ˆ˜(reset, getEvents)๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • handlers: ์ƒ์„ฑ๋œ ์Šค์ฝ”ํ”„์˜ ์ƒํƒœ๋ฅผ ์กฐ์ž‘ํ•˜๋Š” MSW ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ ๋ฐฐ์—ด์ž…๋‹ˆ๋‹ค.
  • reset(): ํ•ธ๋“ค๋Ÿฌ์˜ ์ƒํƒœ๋ฅผ initialState๋กœ ์ดˆ๊ธฐํ™”ํ•˜์—ฌ ํ…Œ์ŠคํŠธ ๊ฐ„ ์ƒํƒœ ์˜ค์—ผ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • getEvents(): ํ…Œ์ŠคํŠธ ์ค‘ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ณ  ๊ฒ€์ฆ(assertion)ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

๊ตฌํ˜„ํ•˜๊ธฐ ์ „, ๋‘ ๊ฐ€์ง€ ์ ‘๊ทผ ๋ฐฉ์‹์„ ๊ณ ๋ คํ–ˆ์Šต๋‹ˆ๋‹ค.

1. ๋…๋ฆฝ์ ์ธ MSW ์„œ๋ฒ„ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ ๋‹ค.

[๊ตฌํ˜„ ๋ฐฉ์‹]

export const createIsolatedTestServer = () => {
  const isolatedServer = setupServer();
  const testEvents: Event[] = [];

  isolatedServer.use(
    http.get('/api/events', () => {
      return HttpResponse.json([...testEvents]);
    })
    // ... ๊ธฐํƒ€ ํ•ธ๋“ค๋Ÿฌ
  );

  return { server: isolatedServer, events: testEvents };
};

[์žฅ์ ]

  • ์™„๋ฒฝํ•œ ๊ฒฉ๋ฆฌ๋ฅผ ๋ณด์žฅํ•˜์—ฌ ํ…Œ์ŠคํŠธ ๊ฐ„ ์ƒํ˜ธ์ž‘์šฉ์„ ์›์ฒœ์ ์œผ๋กœ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค.

[๋‹จ์ ]

  • ๊ฐ ํ…Œ์ŠคํŠธ๋งˆ๋‹ค ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๋ณต์žก์„ฑ์ด ์ฆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์„ฑ๋Šฅ ์ €ํ•˜ ๋ฐ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2. ํด๋กœ์ € ํ™œ์šฉํ•œ ์ƒํƒœ ๊ฒฉ๋ฆฌ (ํ˜„์žฌ ๋ฐฉ์‹)

ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜ ๋‚ด์— ์ƒํƒœ๋ฅผ ์ง€์—ญ ๋ณ€์ˆ˜๋กœ ๋‘์–ด, ํด๋กœ์ €๋ฅผ ํ†ตํ•ด ์ƒํƒœ๋ฅผ ์บก์Аํ™”ํ•˜๊ณ  ๊ฒฉ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

[์žฅ์ ]

  • ๊ตฌํ˜„์ด ๊ฐ„๋‹จํ•˜๊ณ  ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค.
  • ๊ธฐ์กด MSW ์„œ๋ฒ„ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ณต์œ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ ์ €ํ•˜์˜ ์šฐ๋ ค๊ฐ€ ์ ์Šต๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ๋ณ„ ๋…๋ฆฝ์ ์ธ ์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  • ๋น ๋ฅธ ํ…Œ์ŠคํŠธ ์‹คํ–‰

[๋‹จ์ ]

  • ์ž˜๋ชป ์‚ฌ์šฉ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๊ฐ€๋Šฅ์„ฑ

[์„ ํƒ ๊ธฐ์ค€ ๋ฐ ํŠธ๋ ˆ์ด๋“œ ์˜คํ”„]

  • ๋‹จ์ˆœ์„ฑ๊ณผ ์‹ค์šฉ์„ฑ์— ์ดˆ์ ์„ ๋งž์ถ”์—ˆ์Šต๋‹ˆ๋‹ค.
  • ํด๋กœ์ €๋ฅผ ํ™œ์šฉํ•œ ๋ฐฉ๋ฒ•์€ ์ถฉ๋ถ„ํ•œ ์ˆ˜์ค€์˜ ๊ฒฉ๋ฆฌ๋ฅผ ๊ฐ„๋‹จํ•˜๊ณ  ์ง๊ด€์ ์ธ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค. beforeEach์™€ afterEach ๊ฐ™์€ ํ…Œ์ŠคํŠธ ์ƒ๋ช…์ฃผ๊ธฐ ํ›…(hook)๊ณผ ๊ฒฐํ•ฉํ•˜์—ฌ ๋ช…ํ™•ํ•œ ํŒจํ„ด์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์‹ค์ˆ˜ํ•  ์—ฌ์ง€๋ฅผ ์ตœ์†Œํ™”ํ•˜๋ฉด์„œ ํ…Œ์ŠคํŠธ์˜ ๋…๋ฆฝ์„ฑ์„ ํšจ๊ณผ์ ์œผ๋กœ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  • ํ•ธ๋“ค๋Ÿฌ ์ƒ์„ฑ๊ณผ ๋“ฑ๋ก์„ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌํ•˜๊ณ ์ž ํ–ˆ์Šต๋‹ˆ๋‹ค.


Q. ํ…Œ์ŠคํŠธ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๊ตฌ๋™์‹œํ‚ค๊ธฐ ์œ„ํ•ด ์ž‘์„ฑํ–ˆ๋˜ ์„ค์ •๋“ค์„ ์†Œ๊ฐœํ•ด์ฃผ์„ธ์š”.

1. Setup๊ณผ Teardown ์ ์šฉ

  let mockHandlers: ReturnType<typeof createMockHandlers>;

  beforeEach(() => {
    mockHandlers = createMockHandlers();
    server.use(...mockHandlers.handlers);
  });

  afterEach(() => {
    mockHandlers.reset();
    server.resetHandlers();
    vi.clearAllMocks();
  });

๊ฐ ํ…Œ์ŠคํŠธ๊ฐ€ ์ด์ „ ํ…Œ์ŠคํŠธ์˜ ์ƒํƒœ์— ์˜ํ–ฅ ๋ฐ›์ง€ ์•Š๊ณ , ํ•ญ์ƒ ๋…๋ฆฝ์ ์ธ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด beforeEach์™€ afterEach ์ƒ๋ช…์ฃผ๊ธฐ ํ›…์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

  • beforeEach (๊ฐ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ง์ „)

    • mockHandlers = createMockHandlers() ๋งค ํ…Œ์ŠคํŠธ๊ฐ€ ์‹œ์ž‘๋˜๊ธฐ ์ง์ „, createMockHandlers๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๊ณ ์œ ํ•œ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ๊ฐ€์ง„ ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ๋ชฉ ํ•ธ๋“ค๋Ÿฌ ์„ธํŠธ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด์ „ ํ…Œ์ŠคํŠธ์—์„œ ๋ณ€๊ฒฝ๋œ ์ƒํƒœ๊ฐ€ ํ˜„์žฌ ํ…Œ์ŠคํŠธ์— ์ ˆ๋Œ€ ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
    • server.use(...) ์ƒ์„ฑ๋œ ํ•ธ๋“ค๋Ÿฌ๋ฅผ MSW ์„œ๋ฒ„์— ์ ์šฉํ•˜์—ฌ ํ˜„์žฌ ํ…Œ์ŠคํŠธ๊ฐ€ ์ด ๊ฒฉ๋ฆฌ๋œ ํ•ธ๋“ค๋Ÿฌ๋งŒ์„ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • afterEach (๊ฐ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์งํ›„)

    • mockHandlers.reset() ํ˜„์žฌ ํ…Œ์ŠคํŠธ์—์„œ ์‚ฌ์šฉํ•œ ํ•ธ๋“ค๋Ÿฌ์˜ ์ƒํƒœ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•˜์—ฌ ๋ฆฌ์…‹ํ•ฉ๋‹ˆ๋‹ค.
    • server.resetHandlers() MSW ์„œ๋ฒ„์— ๋“ฑ๋ก๋œ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ชจ๋‘ ์ œ๊ฑฐํ•˜์—ฌ ์„œ๋ฒ„ ์ธ์Šคํ„ด์Šค ์ž์ฒด๋ฅผ ๊นจ๋—ํ•œ ์ƒํƒœ๋กœ ๋˜๋Œ๋ฆฝ๋‹ˆ๋‹ค.
    • vi.clearAllMocks() ๋ชจ๋“  ๋ชจ์˜(mock) ํ•จ์ˆ˜์˜ ํ˜ธ์ถœ ๊ธฐ๋ก์„ ์ง€์›Œ ๋‹ค์Œ ํ…Œ์ŠคํŠธ์˜ ํ˜ธ์ถœ ํšŸ์ˆ˜ ๊ฒ€์ฆ(assertion)์ด ์ •ํ™•ํ•˜๊ฒŒ ์ด๋ฃจ์–ด์ง€๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

2. renderProvider

export function renderWithProvider(
  ui: ReactElement,
  options?: Omit<RenderOptions, 'wrapper'>
): RenderResult {
  return render(ui, {
    wrapper: ({ children }) => <Provider>{children}</Provider>,
    ...options,
  });
}

[์‚ฌ์šฉ ์˜ˆ์‹œ]

  it('์ด๋ฒคํŠธ ๋ชฉ๋ก์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ Œ๋”๋ง๋œ๋‹ค', () => {
      renderWithProvider(<EventList {...defaultProps} />);

์•ž์—์„œ ์–ธ๊ธ‰ํ•œ ์ƒ๋ช…์ฃผ๊ธฐ ํ›…์ด ํ…Œ์ŠคํŠธ ์™ธ๋ถ€ ํ™˜๊ฒฝ(์„œ๋ฒ„, ๋ชจํ‚น)์— ๊ด€๋ จํ•œ ์ ์šฉ์ด๋ผ๋ฉด, renderWithProvider๋Š” ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ์ปดํฌ๋„ŒํŠธ์˜ ๋‚ด๋ถ€ ์‹คํ–‰ ํ™˜๊ฒฝ์„ ๋…๋ฆฝ์ ์œผ๋กœ ๊ตฌ์„ฑํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

  1. ์ผ๊ด€๋œ ๋ Œ๋”๋ง ํ™˜๊ฒฝ ์ œ๊ณต
  • ํ…Œ์ŠคํŠธํ•˜๋ ค๋Š” ์ปดํฌ๋„ŒํŠธ(ui)๊ฐ€ ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ฒ˜๋Ÿผ ํ•„์š”ํ•œ Provider (์˜ˆ: Redux, React Query, ThemeProvider ๋“ฑ) ๋‚ด๋ถ€์—์„œ ๋ Œ๋”๋ง๋˜๋„๋ก ์ผ๊ด€๋œ ํ™˜๊ฒฝ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ์ด ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ์—†์ด ์–ด๋–ค ํ…Œ์ŠคํŠธ์—์„œ๋Š” Provider๋ฅผ ๊ฐ์‹ธ๊ณ , ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ์—์„œ๋Š” ๊ฐ์‹ธ์ง€ ์•Š๋Š”๋‹ค๋ฉด ํ…Œ์ŠคํŠธ๋Š” ๋ถˆ์•ˆ์ •ํ•ด์ง‘๋‹ˆ๋‹ค. renderWithProvider๋กœ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ๋™์ผํ•œ ํ™˜๊ฒฝ์—์„œ ์‹œ์ž‘ํ•˜๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•˜์—ฌ ์˜ˆ์ธก ๊ฐ€๋Šฅ์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  1. ์ƒํƒœ(State)์˜ ๊ฒฉ๋ฆฌ
  • Provider๋Š” ๋ณดํ†ต ์•ฑ์˜ ์ „์—ญ ์ƒํƒœ(global state)๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์—ฌ๋Ÿฌ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•˜๋‚˜์˜ ๊ณต์œ ๋œ Provider ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ํ•œ ํ…Œ์ŠคํŠธ์—์„œ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ–ˆ์„ ๋•Œ ๋‹ค์Œ ํ…Œ์ŠคํŠธ์— ์˜ํ–ฅ์„ ๋ฏธ์ณ ํ…Œ์ŠคํŠธ์˜ ๋…๋ฆฝ์„ฑ์ด ๊นจ์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  • renderWithProvider๋Š” ๊ฐ ํ…Œ์ŠคํŠธ๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ๋งˆ๋‹ค ๋‚ด๋ถ€์ ์œผ๋กœ ์ƒˆ๋กœ์šด Provider ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด Provider๊ฐ€ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์ƒˆ๋กœ ์ƒ์„ฑํ•˜๋„๋ก ๊ตฌ์„ฑ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ ํ…Œ์ŠคํŠธ๋Š” ๊ฒฉ๋ฆฌ๋œ ์ƒํƒœ ์ €์žฅ์†Œ๋ฅผ ๊ฐ€์ง€๊ณ  ์‹œ์ž‘ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

3. ํŒฉํ† ๋ฆฌ ํŒจํ„ด์„ ํ™œ์šฉํ•œ ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ

export const createTestEvent = (overrides: Partial<Event> = {}): Event => {
  const baseEvent = events.events[0];
  return { ...baseEvent, ...overrides } as Event;
};
  • ๋ฐ˜๋ณต๋˜๋Š” ๋ชฉ ์ด๋ฒคํŠธ ์ƒ์„ฑ์˜ ์ค‘๋ณต์„ ์ค„์ด๊ณ ์ž ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜๋ฅผ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
  • createTestEvent ๊ธฐ๋ณธ ์ด๋ฒคํŠธ ๊ฐ์ฒด์— overrides๋ฅผ ์ ์šฉํ•˜์—ฌ ๋งค๋ฒˆ ์ƒˆ๋กœ์šด ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ณ ์œ ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ํ…Œ์ŠคํŠธ ๊ฐ„ ์ƒํ˜ธ ๊ฐ„์„ญ ๋ฐ ๋ถ€์ž‘์šฉ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค (๋…๋ฆฝ์„ฑ ๋ณด์žฅ)
  • ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ๋ณ€๊ฒฝ ์‹œ, ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋ชจ๋“  ๊ด€๋ จ ํ…Œ์ŠคํŠธ์— ๋ฐ˜์˜๋˜์–ด ์œ ์ง€๋ณด์ˆ˜์— ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ์˜ ๋ณต์žก์„ฑ์ด ์ค„์–ด๋“ค์–ด ํ…Œ์ŠคํŠธ์˜ ํ•ต์‹ฌ ๋กœ์ง์„ ํŒŒ์•…ํ•˜๊ธฐ ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค.

์‹ฌํ™” ๊ณผ์ œ

  • App ์ปดํฌ๋„ŒํŠธ ์ ์ ˆํ•œ ๋‹จ์œ„์˜ ์ปดํฌ๋„ŒํŠธ, ํ›…, ์œ ํ‹ธ ํ•จ์ˆ˜๋กœ ๋ถ„๋ฆฌํ–ˆ๋Š”๊ฐ€?
  • ํ•ด๋‹น ๋ชจ๋“ˆ๋“ค์— ๋Œ€ํ•œ ์ ์ ˆํ•œ ํ…Œ์ŠคํŠธ๋ฅผ 5๊ฐœ ์ด์ƒ ์ž‘์„ฑํ–ˆ๋Š”๊ฐ€?

๊ณผ์ œ ์…€ํ”„ํšŒ๊ณ 

์ €๋Š” ํ…Œ์ŠคํŠธ์ฝ”๋“œ์— ๋Œ€ํ•œ ๊ฒฝํ—˜์ด playwright ์œผ๋กœ ์œ ์ € ์‹œ๋‚˜๋ฆฌ์˜ค ๋ช‡ ๊ฐœ ์ž‘์„ฑํ•ด๋ณธ ๊ฒฝํ—˜๊ณผ ์œ ๋‹› ํ…Œ์ŠคํŠธ๋ฅผ ai๋กœ ์ž‘์„ฑํ•ด๋ณธ ๊ฒฝํ—˜ ๋ฐ–์— ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋ฒˆ ๊ณผ์ œ์—์„œ ์ ์šฉ๋ณด๋‹ค๋Š” ์šฉ์–ด, ๊ฐœ๋… ์œ„์ฃผ๋กœ ์ดํ•ด ๋จผ์ € ํ•˜๊ณ  ์‹ค์Šต์„ ํ•˜๊ณ ์ž ํ–ˆ์Šต๋‹ˆ๋‹ค. ์—ญ์‹œ๋‚˜ ์ง„๋„๊ฐ€ ๋”๋””๊ฒŒ ๋˜์—ˆ๊ณ , ์ฒ˜์Œ์—๋Š” ์–ด๋–ค ํ๋ฆ„์œผ๋กœ ์ž‘์„ฑํ•ด์•ผํ• ์ง€ ๊ฐ๋„ ์•ˆ์žกํ˜”์Šต๋‹ˆ๋‹ค. e2e๋Š” ์ œ๊ฐ€ ์‚ฌ์šฉ์ž๋กœ์„œ์˜ ๊ฒฝํ—˜๋„ ์žˆ๊ธฐ์— "์–ด๋–ค ํ๋ฆ„์œผ๋กœ ์ž‘์„ฑํ•˜๋ฉด ๋˜๊ฒ ๋‹ค" ๋ผ๋Š” ๊ฐ๊ฐ์ด๋ผ๋„ ์žˆ์—ˆ์ง€๋งŒ, ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ๊ทธ ๋กœ์ง์˜ ํ๋ฆ„์„ ์ •ํ™•ํ•˜๊ฒŒ ์ดํ•ดํ•ด์•ผ ํ–ˆ๊ณ , ๊ทธ ์ž์ฒด๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ๋กœ์ง์„ ์ƒ๊ฐํ•˜๊ณ  ๊ตฌํ˜„ํ–ˆ๋‹ค๊ณ  ํ•ด๋„ ๊ทธ๊ฒƒ์— ๋Œ€ํ•œ ํ™•์‹ ์ด ๋ถ€์กฑํ–ˆ์Šต๋‹ˆ๋‹ค. ์ง„ํ–‰ํ•˜๋ฉด์„œ๋„ "์ด๊ฒŒ ๊ณผ์—ฐ ์ด ํ•จ์ˆ˜๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ํ…Œ์ŠคํŠธ์ธ๊ฐ€? ์œ ์˜๋ฏธํ•œ๊ฐ€?" ๋ฅผ ๊ณ„์† ์ƒ๊ฐํ–ˆ์ง€๋งŒ, ํ™•์‹ ์€ ์—†์–ด ์–ด๋ ต๊ฒŒ ๋А๊ปด์กŒ์Šต๋‹ˆ๋‹ค. ํ™”์š”์ผ์— ์˜คํ”„์ฝ”์น˜๋‹˜๊ป˜์„œ ์ฃผ์‹  ๊ฐ•์˜๊ฐ€ ๋„์›€์ด ๋งŽ์ด ๋˜์—ˆ์ง€๋งŒ ์‹œ๊ฐ„์ƒ ๋‹น์žฅ ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ๋„์—„๋„์—„ ๋ณธ ๊ฒƒ์ด ์•„์‰ฌ์› ์Šต๋‹ˆ๋‹ค. ๊ฐ•์˜๋ฅผ ๋‹ค ๋ณด๋ฉด ์ •๋ฆฌ๊ฐ€ ๋  ๊ฒƒ ๊ฐ™์€๋ฐ, ์ž˜ ์ดํ•ด๋˜์ง€ ์•Š์€ ์ƒํƒœ์—์„œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ ๊ฒƒ์ด ์•„์‰ฌ์› ์Šต๋‹ˆ๋‹ค.


๊ธฐ์ˆ ์  ์„ฑ์žฅ

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด์„œ ์ƒˆ๋กญ๊ฒŒ ์•Œ๊ฒŒ ๋œ ๊ฐœ๋…๋“ค์„ ํ•˜๋‚˜์”ฉ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

React Testing Library & Jest ์ฃผ์š” ๊ฐœ๋… ์‹ฌํ™” ์ •๋ฆฌ

1. describe

  • Jest ๊ณต์‹ ๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด describe๋Š” "ํ•จ๊ป˜ ํ…Œ์ŠคํŠธํ•  ๊ด€๋ จ ํ…Œ์ŠคํŠธ๋“ค์„ ๊ทธ๋ฃนํ™”ํ•˜๊ธฐ ์œ„ํ•œ ๋ธ”๋ก"์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฃนํ™”๋œ ํ…Œ์ŠคํŠธ๋“ค์€ describe ๋ธ”๋ก์ด ์‹คํ–‰๋  ๋•Œ ํ•˜๋‚˜์˜ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.

[์˜ˆ์‹œ]

describe('๋กœ๊ทธ์ธ ํผ ํ…Œ์ŠคํŠธ', () => {
  it('ํผ ์š”์†Œ๋“ค์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ Œ๋”๋ง๋˜์–ด์•ผ ํ•œ๋‹ค', () => {
    // ํ…Œ์ŠคํŠธ ์ฝ”๋“œ...
  });

  it('ํผ ์ œ์ถœ ์‹œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ†ต๊ณผํ•ด์•ผ ํ•œ๋‹ค', () => {
    // ํ…Œ์ŠคํŠธ ์ฝ”๋“œ...
  });
});
  • describe๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ…Œ์ŠคํŠธ๋ฅผ ๋…ผ๋ฆฌ์ ์œผ๋กœ ๋ฌถ์–ด์ฃผ๋ฏ€๋กœ, ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ๋ณด๊ณ ์„œ๋ฅผ ๋ณผ ๋•Œ ๊ฐ€๋…์„ฑ์ด ๋†’์•„์ง€๊ณ  ์‹คํŒจํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ์–ด๋–ค ๊ธฐ๋Šฅ๊ณผ ๊ด€๋ จ ์žˆ๋Š”์ง€ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2. it / test

  • Jest ๊ณต์‹ ๋ฌธ์„œ์—์„œ it๊ณผ test๋Š” ์„œ๋กœ์˜ ๋ณ„์นญ(alias)์ด๋ฉฐ, ๊ธฐ๋Šฅ์ ์œผ๋กœ ์™„์ „ํžˆ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.
  • ๊ถŒ์žฅ ์‚ฌ์šฉ๋ฒ•: ๊ด€์Šต์ ์œผ๋กœ describe ๋ธ”๋ก ์•ˆ์—์„œ it์„ ์‚ฌ์šฉํ•ด "it should do something"๊ณผ ๊ฐ™์€ ์˜์–ด ๋ฌธ์žฅ์ฒ˜๋Ÿผ ํ…Œ์ŠคํŠธ ์„ค๋ช…์„ ์ž‘์„ฑํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ test ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด "test if..."์ฒ˜๋Ÿผ ๋ช…ํ™•ํ•˜๊ฒŒ ํ…Œ์ŠคํŠธ๋ฅผ ์ •์˜ํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์–ด๋–ค ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋“  ํŒ€ ๋‚ด์—์„œ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

3. renderHook

  • React Testing Library๋Š” ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง์— ํŠนํ™”๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ง€๋งŒ, renderHook์€ DOM ์š”์†Œ ์—†์ด ์ปค์Šคํ…€ ํ›…์˜ ๋กœ์ง๋งŒ์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก ํŠน๋ณ„ํžˆ ์ œ๊ณต๋˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ํ›…์˜ ๋ฐ˜ํ™˜๊ฐ’์ด๋‚˜ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ์ฃผ์š” ์šฉ๋„: useSWR์ฒ˜๋Ÿผ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ํŒจ์นญ ๋กœ์ง์„ ๊ฐ€์ง„ ํ›…์ด๋‚˜, ๋ณต์žกํ•œ ์ƒํƒœ ๊ด€๋ฆฌ ๋กœ์ง์„ ๊ฐ€์ง„ ํ›…์„ ํ…Œ์ŠคํŠธํ•  ๋•Œ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

4. wrapper

  • render ํ•จ์ˆ˜์˜ ์˜ต์…˜์œผ๋กœ ์ œ๊ณต๋˜๋Š” wrapper๋Š” ํ…Œ์ŠคํŠธํ•  ์ปดํฌ๋„ŒํŠธ๋ฅผ ํŠน์ • ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐ์‹ธ๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ Context Provider๋‚˜ ThemeProvider์ฒ˜๋Ÿผ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํŠน์ • ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์— ์˜์กดํ•˜๋Š” ๊ฒฝ์šฐ์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ์žฅ์ : wrapper ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋งˆ๋‹ค ๋™์ผํ•œ Provider๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ์ž‘์„ฑํ•˜๋Š” ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. baseElement

  • render ํ•จ์ˆ˜์˜ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋กœ, ์ฟผ๋ฆฌ๋ฅผ ๊ฒ€์ƒ‰ํ•  DOM์˜ ๊ธฐ๋ณธ ์š”์†Œ๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฃผ์š” ์šฉ๋„: document.body๊ฐ€ ์•„๋‹Œ ํŠน์ • ์ปจํ…Œ์ด๋„ˆ ์•ˆ์—์„œ๋งŒ ์š”์†Œ๋ฅผ ์ฐพ์•„์•ผ ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ชจ๋‹ฌ์ด๋‚˜ ํŒ์—… ์ปดํฌ๋„ŒํŠธ๋Š” document.body์˜ ์ง์† ์ž์‹์ด ์•„๋‹ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง๋˜๋Š” ๋ถ€๋ชจ ์š”์†Œ๋ฅผ baseElement๋กœ ์ง€์ •ํ•˜์—ฌ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

6. toBe vs toEqual vs toStrictEqual

  • Jest์˜ ๋งค์ฒ˜(Matcher)๋“ค์€ ๊ฐ’์˜ ๋น„๊ต ๋ฐฉ์‹์— ๋”ฐ๋ผ ์—„๊ฒฉ๋„๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.
    • toBe: ์›์‹œ ํƒ€์ž…(์ˆซ์ž, ๋ฌธ์ž์—ด, ๋ถˆ๋ฆฌ์–ธ)์˜ **๋™์ผ์„ฑ(===)**์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๊ฐ์ฒด๋Š” ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ๊นŒ์ง€ ๋น„๊ตํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ ๊ฐ์ฒด๋ผ๋ฉด ๋‚ด์šฉ์ด ๊ฐ™์•„๋„ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.
    • toEqual: ๊ฐ์ฒด๋‚˜ ๋ฐฐ์—ด์˜ ๋‚ด์šฉ(๊ฐ’)์„ ์žฌ๊ท€์ ์œผ๋กœ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. expect({ a: 1 }).toEqual({ a: 1 })์€ ํ†ต๊ณผํ•˜์ง€๋งŒ, expect({ a: 1 }).toBe({ a: 1 })์€ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.
    • toStrictEqual: toEqual๋ณด๋‹ค ๋” ์—„๊ฒฉํ•œ ๋น„๊ต๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ์ฒด์˜ ๋‚ด์šฉ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์†์„ฑ์˜ undefined ์—ฌ๋ถ€, ๋ฐฐ์—ด์˜ ํฌ์†Œ์„ฑ(sparse array) ๋“ฑ๊นŒ์ง€ ์ •ํ™•ํ•˜๊ฒŒ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, expect([1, undefined]).toStrictEqual([1])์€ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

7. act

  • React ๊ณต์‹ ๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด act๋Š” "๋ Œ๋”๋ง ๋ฐ ์ƒํƒœ ์—…๋ฐ์ดํŠธ์™€ ๊ด€๋ จ๋œ ๋ชจ๋“  ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋„๋ก ๋ณด์žฅ"ํ•˜๋Š” ํ—ฌํผ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. React๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์—ฌ๋Ÿฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ๋ฐฐ์น˜(batch)ํ•˜์—ฌ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ, ๋น„๋™๊ธฐ ์ž‘์—… ํ›„ UI๊ฐ€ ์—…๋ฐ์ดํŠธ๋  ๋•Œ act๋กœ ๊ฐ์‹ธ์ฃผ์ง€ ์•Š์œผ๋ฉด ํ…Œ์ŠคํŠธ๊ฐ€ ๋ถˆ์•ˆ์ •ํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ•ต์‹ฌ: render์™€ fireEvent ๊ฐ™์€ React Testing Library ํ•จ์ˆ˜๋“ค์€ ์ด๋ฏธ ๋‚ด๋ถ€์ ์œผ๋กœ act๋กœ ๊ฐ์‹ธ์ ธ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์ง์ ‘ act๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ setTimeout, ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋น„๋™๊ธฐ ํ•จ์ˆ˜, ํ˜น์€ ์‚ฌ์šฉ์ž ์ •์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ React ํ…Œ์ŠคํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์™ธ๋ถ€์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋น„๋™๊ธฐ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ๋Š” ๋ช…์‹œ์ ์œผ๋กœ await act(...)๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

8. findBy...

  • findBy๋Š” ๋น„๋™๊ธฐ์ ์œผ๋กœ ์š”์†Œ๋ฅผ ์ฐพ๋Š” ์ฟผ๋ฆฌ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. Testing Library ๊ณต์‹ ๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด findBy ๊ณ„์—ด์€ getBy ์ฟผ๋ฆฌ์™€ waitFor ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ๊ฒฐํ•ฉํ•œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.
  • ํŠน์ง•:
    • Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์š”์†Œ๊ฐ€ ๋‚˜ํƒ€๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.
    • ์ง€์ •๋œ ๊ธฐ๋ณธ ํƒ€์ž„์•„์›ƒ(1000ms) ๋‚ด์— ์š”์†Œ๋ฅผ ์ฐพ์ง€ ๋ชปํ•˜๋ฉด Promise๊ฐ€ reject๋ฉ๋‹ˆ๋‹ค.
  • ์ฃผ์š” ์šฉ๋„: API ํ˜ธ์ถœ ํ›„ ๋ฐ์ดํ„ฐ๊ฐ€ ํ™”๋ฉด์— ๋‚˜ํƒ€๋‚˜๋Š” ๊ฒƒ์„ ๊ธฐ๋‹ค๋ฆฌ๊ฑฐ๋‚˜, ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋๋‚œ ํ›„ UI๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ์„ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์‹œ ๊ผญ ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ•  ๊ธฐ๋ณธ ์›์น™

React Testing Library์˜ ํ•ต์‹ฌ ์ฒ ํ•™์€ "์‚ฌ์šฉ์ž๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹๊ณผ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ๋น„์Šทํ• ์ˆ˜๋ก, ๋” ๋†’์€ ์‹ ๋ขฐ๋„๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค"๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ์ฒ ํ•™์— ๊ธฐ๋ฐ˜ํ•œ ์ฃผ์š” ์›์น™์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

1. ์‚ฌ์šฉ์ž์˜ ํ–‰๋™์„ ํ…Œ์ŠคํŠธํ•˜๋ผ (Test User Behavior)

  • ๋ฌด์—‡์„ ํ•  ๊ฒƒ์ธ๊ฐ€? ์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ณ , ํผ์— ๋‚ด์šฉ์„ ์ž…๋ ฅํ•˜๋ฉฐ, ํ™”๋ฉด์— ํŠน์ • ํ…์ŠคํŠธ๊ฐ€ ๋‚˜ํƒ€๋‚˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉ์ž์˜ ๊ด€์ ์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฌด์—‡์„ ํ”ผํ•  ๊ฒƒ์ธ๊ฐ€? ์ปดํฌ๋„ŒํŠธ์˜ ๋‚ด๋ถ€ ์ƒํƒœ(์˜ˆ: useState์˜ ๊ฐ’)๋‚˜ ํŠน์ • CSS ํด๋ž˜์Šค ์ด๋ฆ„, ๊ตฌํ˜„ ๋””ํ…Œ์ผ(div, span ๋“ฑ)์— ์˜์กดํ•˜๋Š” ํ…Œ์ŠคํŠธ๋Š” ํ”ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฐ ํ…Œ์ŠคํŠธ๋Š” ๋ฆฌํŒฉํ† ๋ง ์‹œ ์‰ฝ๊ฒŒ ๊นจ์ง€๋ฉฐ, ์œ ์ง€๋ณด์ˆ˜ ๋น„์šฉ์ด ๋†’์•„์ง‘๋‹ˆ๋‹ค.

2. ๊ตฌํ˜„ ์„ธ๋ถ€ ์ •๋ณด๋Š” ํ…Œ์ŠคํŠธํ•˜์ง€ ๋งˆ๋ผ (Don't Test Implementation Details)

  • getByTestId๋Š” ์ตœํ›„์˜ ์ˆ˜๋‹จ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ , ์‚ฌ์šฉ์ž์—๊ฒŒ ์˜๋ฏธ ์žˆ๋Š” ์ฟผ๋ฆฌ(ByRole, ByLabelText, ByText, ByPlaceholderText)๋ฅผ ์šฐ์„ ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, screen.getByRole('button', { name: /ํด๋ฆญ/i })์ฒ˜๋Ÿผ ์‚ฌ์šฉ์ž์˜ ์ ‘๊ทผ์„ฑ(accessibility)์„ ๊ณ ๋ คํ•œ ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋” ๊ฒฌ๊ณ ํ•˜๊ณ  ์œ ์šฉํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์ž‘๊ณ  ์ง‘์ค‘๋œ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋ผ (Write Small, Focused Tests)

  • ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ(it ๋ธ”๋ก)๋Š” ํ•˜๋‚˜์˜ ํŠน์ • ๊ธฐ๋Šฅ์„ ๊ฒ€์ฆํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ์„ ํ•œ ํ…Œ์ŠคํŠธ์— ์„ž์ง€ ๋งˆ์„ธ์š”.
  • describe ๋ธ”๋ก์„ ํ™œ์šฉํ•˜์—ฌ ๊ด€๋ จ ํ…Œ์ŠคํŠธ๋ฅผ ๊ทธ๋ฃนํ™”ํ•˜๊ณ , ๊ฐ ํ…Œ์ŠคํŠธ๋Š” ๋ช…ํ™•ํ•œ ํ•˜๋‚˜์˜ ๋ชฉํ‘œ๋ฅผ ๊ฐ€์ง€๋„๋ก ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ…Œ์ŠคํŠธ ์‹คํŒจ ์‹œ ์›์ธ์„ ํŒŒ์•…ํ•˜๊ธฐ ์‰ฝ๊ณ , ๋””๋ฒ„๊น… ์‹œ๊ฐ„์„ ๋‹จ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๊ฐ€๋…์„ฑ ์žˆ๊ฒŒ ์ž‘์„ฑํ•˜๋ผ

  • ํ…Œ์ŠคํŠธ ์„ค๋ช…์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ž‘์„ฑํ•˜์—ฌ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ฌด์—‡์„ ๊ฒ€์ฆํ•˜๋Š”์ง€ ํ•œ๋ˆˆ์— ์•Œ ์ˆ˜ ์žˆ๊ฒŒ ํ•˜์„ธ์š”.
  • AAA ํŒจํ„ด(Arrange-Act-Assert)์„ ๋”ฐ๋ฅด๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • Arrange: ํ…Œ์ŠคํŠธ์— ํ•„์š”ํ•œ ํ™˜๊ฒฝ ๋ฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค. (render(), Mock ๋ฐ์ดํ„ฐ ๋“ฑ)
  • Act: ์‚ฌ์šฉ์ž์˜ ํ–‰๋™์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•ฉ๋‹ˆ๋‹ค. (fireEvent.click(), fireEvent.change(), userEvent.type())
  • Assert: ๊ธฐ๋Œ€ ๊ฒฐ๊ณผ๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. (expect().toBeInTheDocument())

์ฝ”๋“œ ํ’ˆ์งˆ

๋ฆฌํŒฉํ† ๋ง์ด ํ•„์š”ํ•œ ๋ถ€๋ถ„ (EventForm)

  1. ๋ฌธ์ œ ๋งŽ์€ props ๊ฐœ์ˆ˜์™€ ๋ณต์žก์„ฑ
const defaultProps = {
    events: mockEvents,
    title: '',
    setTitle: vi.fn(),
    date: '',
    setDate: vi.fn(),
    startTime: '',
    endTime: '',
    description: '',
    setDescription: vi.fn(),
    location: '',
    setLocation: vi.fn(),
    category: '์—…๋ฌด',
    setCategory: vi.fn(),
    isRepeating: false,
    setIsRepeating: vi.fn(),
    repeatType: 'none' as const,
    repeatInterval: 1,
    repeatEndDate: undefined,
    notificationTime: 10,
    setNotificationTime: vi.fn(),
    startTimeError: null,
    endTimeError: null,
    editingEvent: null,
    handleStartTimeChange: vi.fn(),
    handleEndTimeChange: vi.fn(),
    resetForm: vi.fn(),
    onSaveEvent: vi.fn(),
    onOverlapDetected: vi.fn(),
  };
  • ๊ทน์•…์Šค๋Ÿฌ์šด EventForm์˜ props๋ฅผ ๋ฆฌํŒฉํ† ๋ง์—์„œ ํ•ด๊ฒฐ์„ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค..
  • ์ƒํƒœ๊ฐ’, ์ƒํƒœ ์„ค์ • ํ•จ์ˆ˜, ์—๋Ÿฌ ์ƒํƒœ, ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ ๋‹ค์–‘ํ•œ ํƒ€์ž… ํ˜ผ์žฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋นˆ ๋ฌธ์ž์—ด, null, vi.fn() ๋“ฑ ํ…Œ์ŠคํŠธ์šฉ ๊ธฐ๋ณธ๊ฐ’๋“ค์ด ์„ž์—ฌ ์žˆ์Šต๋‹ˆ๋‹ค.
  1. ํ…Œ์ŠคํŠธ๋ณ„ Props ์˜ค๋ฒ„๋ผ์ด๋“œ์˜ ๋ณต์žก์„ฑ
it('์‹œ๊ฐ„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์—๋Ÿฌ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์—๋Ÿฌ ํ† ์ŠคํŠธ๊ฐ€ ํ‘œ์‹œ๋œ๋‹ค', async () => {
  renderWithProvider(
    <EventForm
      {...defaultProps}
      title="ํ…Œ์ŠคํŠธ ์ด๋ฒคํŠธ"
      date="2025-01-20"
      startTime="14:00"
      endTime="13:00"
      startTimeError="์‹œ์ž‘ ์‹œ๊ฐ„์€ ์ข…๋ฃŒ ์‹œ๊ฐ„๋ณด๋‹ค ๋นจ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค."
      endTimeError="์ข…๋ฃŒ ์‹œ๊ฐ„์€ ์‹œ์ž‘ ์‹œ๊ฐ„๋ณด๋‹ค ๋Šฆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค."
    />
  );
  // ... ํ…Œ์ŠคํŠธ ๋กœ์ง
});
  • ํ…Œ์ŠคํŠธ๋งˆ๋‹ค props ์„ค์ •์ด ๋‹ฌ๋ผ ๋ณต์žกํ•จ์ด ์ฒด๊ฐ๋ฉ๋‹ˆ๋‹ค.
  1. props ๊ตฌ์กฐ ๋ณ€๊ฒฝ ์‹œ ์˜ํ–ฅ ๋ฒ”์œ„
it('๋ชจ๋“  ์ •๋ณด๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž…๋ ฅ๋œ ๊ฒฝ์šฐ ์ผ์ •์ด ์ €์žฅ๋œ๋‹ค', async () => {
  const validProps = {
    ...defaultProps,
    title: '์ƒˆ ์ด๋ฒคํŠธ',
    date: '2025-01-20',
    startTime: '10:00',
    endTime: '11:00',
    description: 'ํ…Œ์ŠคํŠธ ์„ค๋ช…',
    location: 'ํšŒ์˜์‹ค',
    category: '์—…๋ฌด',
    notificationTime: 10,
  };

  renderWithProvider(<EventForm {...validProps} />);
  // ... ํ…Œ์ŠคํŠธ ๋กœ์ง
});

[๊ทผ๋ณธ์ ์ธ ๋ฌธ์ œ]

  • EventForm ์ปดํฌ๋„ŒํŠธ ์ž์ฒด๊ฐ€ ๋งŽ์€ props๋ฅผ ๋ฐ›๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์€ ์ฑ…์ž„์„ ๊ฐ€์ง€๊ณ  ์žˆ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
  • ๊นŠ์€ ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ์—์„œ props๋ฅผ ๊ณ„์† ์ „๋‹ฌํ•˜๋Š” ๊ตฌ์กฐ๋กœ ์„ค๊ณ„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐ ํ…Œ์ŠคํŠธ๋งˆ๋‹ค ํ•„์š”ํ•œ props๋งŒ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๋Š” ๊ฒƒ์ด ๋ณต์žกํ•จ์„ ์œ ๋ฐœํ•ฉ๋‹ˆ๋‹ค.

[๊ฐœ์„  ๋ฐฉํ–ฅ]

  1. Props ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜ ๋„์ž…
const createEventFormProps = (overrides: Partial<EventFormProps> = {}): EventFormProps => {
  const baseProps: EventFormProps = {
    events: [],
    title: '',
    setTitle: vi.fn(),
    date: '',
    setDate: vi.fn(),
    startTime: '',
    endTime: '',
    description: '',
    setDescription: vi.fn(),
    location: '',
    setLocation: vi.fn(),
    category: '์—…๋ฌด',
    setCategory: vi.fn(),
    isRepeating: false,
    setIsRepeating: vi.fn(),
    repeatType: 'none',
    repeatInterval: 1,
    repeatEndDate: undefined,
    notificationTime: 10,
    setNotificationTime: vi.fn(),
    startTimeError: null,
    endTimeError: null,
    editingEvent: null,
    handleStartTimeChange: vi.fn(),
    handleEndTimeChange: vi.fn(),
    resetForm: vi.fn(),
    onSaveEvent: vi.fn(),
    onOverlapDetected: vi.fn(),
  };

  return { ...baseProps, ...overrides };
};
  1. ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ props ํ—ฌํผ ํ•จ์ˆ˜
const createValidEventFormProps = (overrides: Partial<EventFormProps> = {}) => {
  return createEventFormProps({
    title: '์ƒˆ ์ด๋ฒคํŠธ',
    date: '2025-01-20',
    startTime: '10:00',
    endTime: '11:00',
    description: 'ํ…Œ์ŠคํŠธ ์„ค๋ช…',
    location: 'ํšŒ์˜์‹ค',
    category: '์—…๋ฌด',
    notificationTime: 10,
    ...overrides,
  });
};

const createErrorEventFormProps = (overrides: Partial<EventFormProps> = {}) => {
  return createEventFormProps({
    title: '',
    date: '',
    startTime: '14:00',
    endTime: '13:00',
    startTimeError: '์‹œ์ž‘ ์‹œ๊ฐ„์€ ์ข…๋ฃŒ ์‹œ๊ฐ„๋ณด๋‹ค ๋นจ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค.',
    endTimeError: '์ข…๋ฃŒ ์‹œ๊ฐ„์€ ์‹œ์ž‘ ์‹œ๊ฐ„๋ณด๋‹ค ๋Šฆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.',
    ...overrides,
  });
};

const createEditModeEventFormProps = (editingEvent: Event, overrides: Partial<EventFormProps> = {}) => {
  return createEventFormProps({
    editingEvent,
    title: '์ˆ˜์ •๋œ ์ด๋ฒคํŠธ',
    date: '2025-01-15',
    startTime: '09:00',
    endTime: '10:00',
    ...overrides,
  });
};

[์˜ˆ์‹œ ์ฝ”๋“œ]

describe('ํผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ฐ ์ €์žฅ', () => {
  it('ํ•„์ˆ˜ ์ •๋ณด๊ฐ€ ๋ˆ„๋ฝ๋œ ๊ฒฝ์šฐ ์—๋Ÿฌ ํ† ์ŠคํŠธ๊ฐ€ ํ‘œ์‹œ๋œ๋‹ค', async () => {
    const props = createEventFormProps({
      title: '',
      date: '',
    });

    renderWithProvider(<EventForm {...props} />);

    const saveButton = screen.getByTestId('event-submit-button');
    fireEvent.click(saveButton);

    expect(await screen.findByText('ํ•„์ˆ˜ ์ •๋ณด๋ฅผ ๋ชจ๋‘ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.')).toBeInTheDocument();
    expect(props.onSaveEvent).not.toHaveBeenCalled();
  });

  it('์‹œ๊ฐ„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์—๋Ÿฌ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์—๋Ÿฌ ํ† ์ŠคํŠธ๊ฐ€ ํ‘œ์‹œ๋œ๋‹ค', async () => {
    const props = createErrorEventFormProps();

    renderWithProvider(<EventForm {...props} />);

    const saveButton = screen.getByTestId('event-submit-button');
    fireEvent.click(saveButton);

    expect(await screen.findByText('์‹œ๊ฐ„ ์„ค์ •์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.')).toBeInTheDocument();
    expect(props.onSaveEvent).not.toHaveBeenCalled();
  });

  it('๋ชจ๋“  ์ •๋ณด๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž…๋ ฅ๋œ ๊ฒฝ์šฐ ์ผ์ •์ด ์ €์žฅ๋œ๋‹ค', async () => {
    const props = createValidEventFormProps();

    renderWithProvider(<EventForm {...props} />);

    const saveButton = screen.getByTestId('event-submit-button');
    fireEvent.click(saveButton);

    await waitFor(() => {
      expect(props.onSaveEvent).toHaveBeenCalledWith(
        expect.objectContaining({
          title: '์ƒˆ ์ด๋ฒคํŠธ',
          date: '2025-01-20',
          startTime: '10:00',
          endTime: '11:00',
        })
      );
    });
  });
});

๊ฐœ์„  ํšจ๊ณผ์™€ ๊ธฐ๋Œ€ ๊ฒฐ๊ณผ

  1. ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ
  • ๊ฐ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ฌด์—‡์„ ๊ฒ€์ฆํ•˜๋Š”์ง€ ํ•œ๋ˆˆ์— ํŒŒ์•… ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.
  • ๋ณต์žกํ•œ props ์˜ค๋ฒ„๋ผ์ด๋“œ ๋กœ์ง์— ๋Œ€ํ•ด ํ•ด์†Œ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ์˜ ํ•ต์‹ฌ ๊ฒ€์ฆ ๋กœ์ง์ด props ์„ค์ •์— ๋ฌปํžˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  1. ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ
  • Props ๋ณ€๊ฒฝ ์˜ํ–ฅ์„ ์ตœ์†Œํ™”ํ•˜์—ฌ ์ƒˆ๋กœ์šด prop ์ถ”๊ฐ€ ์‹œ ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜๋งŒ ์ˆ˜์ •
  • ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ์— ์ผ๊ด€์„ฑ์„ ๋ถ€์—ฌํ•˜์—ฌ ๋ชจ๋“  ํ…Œ์ŠคํŠธ์—์„œ ๋™์ผํ•œ ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ props ํ—ฌํผ ํ•จ์ˆ˜๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ค‘๋ณต ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  1. ํ™•์žฅ์„ฑ ํ–ฅ์ƒ
  • ์ƒˆ๋กœ์šด ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค ์ถ”๊ฐ€๊ฐ€ ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค. (ํ—ฌํผ ํ•จ์ˆ˜๋งŒ ์ถ”๊ฐ€ ํ•˜๋ฉด ๋œ๋‹ค)
  • ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜ ์ˆ˜์ •์œผ๋กœ ๋ชจ๋“  ํ…Œ์ŠคํŠธ์— ๋ฐ˜์˜(Props ๊ตฌ์กฐ ๋ณ€๊ฒฝ ๋Œ€์‘)

ํ•™์Šต ํšจ๊ณผ ๋ถ„์„

  • ์•„์ง์€ "์ด๋Ÿฐ๊ฑฐ๊ตฌ๋‚˜" ๋ผ๊ธฐ ๋ณด๋‹ค๋Š” "์ด๋Ÿฐ..๊ฑธ๊นŒ..?" ์˜ ๋А๋‚Œ์ด ๊ฐ•ํ•œ ์‹œ๊ฐ„๋“ค์ด์—ˆ์Šต๋‹ˆ๋‹ค.
  • ๋‹ค์Œ์ฃผ ๊ณผ์ œ๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ฝ๊ณ  ์ •๋ฆฌํ•ด๋ณด๋Š” ์‹œ๊ฐ„์„ ๊ฐ€์ง„๋‹ค๋ฉด ๋‹ค์Œ ๊ณผ์ œ๋ฅผ ๋” ์ž˜ ์†Œํ™”ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ณผ์ œ ํ”ผ๋“œ๋ฐฑ

  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ์— ๋Œ€ํ•ด ๋‚œ์ด๋„๋ณ„๋กœ ์ฒด๊ณ„์ ์œผ๋กœ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑ๋œ ๊ณผ์ œ๋ผ๊ณ  ๋А๊ปด์กŒ์Šต๋‹ˆ๋‹ค.
  • ๋‹ค๋งŒ pass ๋‚œ์ด๋„๊ฐ€ ๋†’๋‹ค๊ณ  ๋А๊ปด์กŒ์Šต๋‹ˆ๋‹ค.ใ… ใ… 

๋ฆฌ๋ทฐ ๋ฐ›๊ณ  ์‹ถ์€ ๋‚ด์šฉ

  1. ์ œ๊ฐ€ ์ž‘์„ฑํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์˜ ์‹ ๋ขฐ๋„๊ฐ€ ๋†’๋‹ค๋Š” ๊ฒƒ์„ ์Šค์Šค๋กœ ํŒ๋‹จํ•˜๋Š” ์ง€ํ‘œ๋ฅผ ์„ธ์šด๋‹ค๋ฉด ์–ด๋–ค๊ฒƒ์„ ๊ธฐ์ค€์œผ๋กœ ์ƒ๊ฐํ•ด๋ณผ ์ˆ˜ ์žˆ์„๊นŒ์š”?
  2. ๋˜ํ•œ ์‹ ๋ขฐ๋„ ์žˆ๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์–ด๋–ค๊ฒƒ์„ ๊ณ ๋ คํ•˜๋ฉด์„œ ์ž‘์„ฑํ•˜๋ฉด ์ข‹์„๊นŒ์š”?
  3. QnA ์‹œ๊ฐ„์— ์œ ์˜๋ฏธํ•œ ๋‹จ์œ„ํ…Œ์ŠคํŠธ์™€ ๊ทธ๋ ‡์ง€ ๋ชปํ•œ ๋‹จ์œ„ํ…Œ์ŠคํŠธ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ด์ฃผ์…จ๋Š”๋ฐ, ์‚ฌ์‹ค ๊ตฌ๋ณ„์„ ์ž˜ ๋ชปํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์— ์ต์ˆ™ํ•ด์ง„๋‹ค๋ฉด ๊ตฌ๋ณ„ํ•˜๊ธฐ ์–ด๋ ต์ง€ ์•Š์€ ๊ฒƒ์ผ๊นŒ์š”? ์ดˆ์‹ฌ์ž๊ฐ€ ์–ด๋–ค ๊ฒƒ์„ ์œ„์ฃผ๋กœ ๊ณต๋ถ€ํ•˜๋ฉด ๋น ๋ฅด๊ฒŒ ๊ฐ๊ฐ์„ ์ตํž ์ˆ˜ ์žˆ์„๊นŒ์š”?

๊ณผ์ œ ํ”ผ๋“œ๋ฐฑ

์™€โ€ฆโ€ฆ ํšŒ๊ณ  ๋‚ด์šฉ์ด ๋ฌด์Šจ ๋…ผ๋ฌธ์ด๋„ค์š” :) ์ง€ํ˜ธ๋‹˜ ๋ฉ‹์ง‘๋‹ˆ๋‹ค.

Q. ์ œ๊ฐ€ ์ž‘์„ฑํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์˜ ์‹ ๋ขฐ๋„๊ฐ€ ๋†’๋‹ค๋Š” ๊ฒƒ์„ ์Šค์Šค๋กœ ํŒ๋‹จํ•˜๋Š” ์ง€ํ‘œ๋ฅผ ์„ธ์šด๋‹ค๋ฉด ์–ด๋–ค๊ฒƒ์„ ๊ธฐ์ค€์œผ๋กœ ์ƒ๊ฐํ•ด๋ณผ ์ˆ˜ ์žˆ์„๊นŒ์š”?

A. ์‚ฌ์‹ค ๊ทธ๊ฒƒ์„ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋Š” ์ง€ํ‘œ๋Š” ์—†๋‹ค๊ณ  ์ƒ๊ฐํ•ด์š”. ์ •์„ฑ์ ์ธ ๊ฒƒ๋ง๊ณ  ์ •๋Ÿ‰์ ์œผ๋กœ๋Š” ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ๋„์›€์ด ๋  ์ˆ˜ ๋Š” ์žˆ์„ ๊ฒƒ ๊ฐ™์•„์š”. ๊ธฐ๋ณธ์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ๊ธฐ์ค€(ํŒ€์—์„œ ์ •ํ•œ ์ „๋žต)์— ๋งž๊ฒŒ ์ž˜ ์ž‘์„ฑํ–ˆ๋‹ค๋Š” ๊ฐ€์ •ํ•˜์— ์–ผ๋งˆ๋‚˜ ์ปค๋ฒ„ํ•˜๊ณ  ์žˆ๋Š”์ง€๋ฅผ ์•Œ ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ์š”. ์ „์ฒด์ ์ธ ์ปค๋ฒ„๋ฆฌ์ง€๋ณด๋‹ค๋Š” ๋ธŒ๋žœ์น˜ ์ปค๋ฒ„๋ฆฌ์ง€๋กœ ์ฝ”๋“œ์˜ ์–ด๋А๋ถ€๋ถ„์ด ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ์‹คํ–‰๋˜์ง€ ์•Š๋Š”์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ •์„ฑ์ ์ธ ์ง€ํ‘œ๋Š” ์•„๋งˆ ์ฐพ๊ธฐ ํž˜๋“ค ๊ฒƒ ๊ฐ™์•„์š”. ์ผ๋‹จ ํ…Œ์ŠคํŠธ๋Š” โ€œ๊นจ์ ธ์•ผ ํ•  ๋•Œ ์ž˜ ๊นจ์ ธ์ฃผ๋ฉดโ€ ์‹ ๋ขฐ๋„๊ฐ€ ๋†’๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๊ณ  "๊นจ์ ธ์•ผ ํ•˜๋Š”๋ฐ ์•ˆ๊นจ์ง€๋ฉด" ์‹ ๋ขฐ๋„๊ฐ€ ๋‚ฎ๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์•„์š”. "์•ˆ๊นจ์ ธ์•ผ ํ•˜๋Š”๋ฐ ๊นจ์ ธ๋„" ๋งˆ์ฐฎ๊ฐ€์ง€๊ฒ ์ฃต.

Q. ์‹ ๋ขฐ๋„ ์žˆ๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์–ด๋–ค๊ฒƒ์„ ๊ณ ๋ คํ•˜๋ฉด์„œ ์ž‘์„ฑํ•˜๋ฉด ์ข‹์„๊นŒ์š”?

์‹ ๋ขฐ๋„๋ฅผ ๋†’์ด๋Š” ๋‹จ ํ•œ๊ฐ€์ง€๋Š” ํ…Œ์ŠคํŠธ๋„ ๋ชจ๋“ˆ๋กœ ์ƒ๊ฐํ•˜๊ณ  ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ๋ชจ๋“ˆ๊ณผ ํ•จ๊ป˜ ๊ณ„์† ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ณ  ์ค‘๋ณต์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  ํ•„์š” ์—†์–ด์ง„ ์ฝ”๋“œ๋Š” ์—†์• ๋Š” ์ž‘์—…์„ ํ•˜๋Š” ๊ฒƒ ๋ฐ–์—๋Š” ์—†๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Q. QnA ์‹œ๊ฐ„์— ์œ ์˜๋ฏธํ•œ ๋‹จ์œ„ํ…Œ์ŠคํŠธ์™€ ๊ทธ๋ ‡์ง€ ๋ชปํ•œ ๋‹จ์œ„ํ…Œ์ŠคํŠธ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ด์ฃผ์…จ๋Š”๋ฐ, ์‚ฌ์‹ค ๊ตฌ๋ณ„์„ ์ž˜ ๋ชปํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์— ์ต์ˆ™ํ•ด์ง„๋‹ค๋ฉด ๊ตฌ๋ณ„ํ•˜๊ธฐ ์–ด๋ ต์ง€ ์•Š์€ ๊ฒƒ์ผ๊นŒ์š”?โ€จ์ดˆ์‹ฌ์ž๊ฐ€ ์–ด๋–ค ๊ฒƒ์„ ์œ„์ฃผ๋กœ ๊ณต๋ถ€ํ•˜๋ฉด ๋น ๋ฅด๊ฒŒ ๊ฐ๊ฐ์„ ์ตํž ์ˆ˜ ์žˆ์„๊นŒ์š”?

A. ์ผ๋‹จ ๋งŽ์ด ์ž‘์„ฑํ•˜๋Š” ์ˆ˜๋ฐ–์—๋Š” ์—†๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค! ๊ด€๋ จ ์ฑ…์„ ์ฐพ์•„์„œ ๋ณด๋ฉด ๋” ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™์•„์š”. ์ œ๊ฐ€ ๊ฒฝํ—˜ํ•ด๋ณธ ๋ฐ”๋กœ๋Š” ํ•œ 1๋…„ ์ •๋„ ์ž‘์„ฑํ•˜๋ฉด ๊ฐ์ด ์ƒ๊ธฐ๊ณ  ์–ด๋–ค ๊ฒƒ์„ ํ…Œ์ŠคํŠธํ•  ๋•Œ๋Š” ์–ด๋–ค ๋ฐฉ๋ฒ•์„์ด๋ผ๋Š” ๋…ธํ•˜์šฐ๊ฐ€ ์ถ•์ ๋๋˜ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.