๋ฐ˜์‘ํ˜•

์ฝ”๋“œ ํ’ˆ์งˆ์„ ๋ณด์žฅํ•˜๊ณ , ๊ธฐ๋Šฅ์ด ์˜๋„ํ•œ ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค. ํŠนํžˆ ๊ธฐ๋Šฅ ์ถ”๊ฐ€๋‚˜ ๋ฆฌํŒฉํ† ๋ง์„ ํ•  ๋•Œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์žˆ์œผ๋ฉด ๊ธฐ์กด ๊ธฐ๋Šฅ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์‰ฝ๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ณ , ์‹ฌ๋ฆฌ์ ์ธ ์•ˆ์ •๊ฐ์„ ์ฃผ๋Š” ์žฅ์ ๋„ ์žˆ๋‹ค. ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ ์ข…๋ฅ˜๋Š” ํฌ๊ฒŒ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ, ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ, E2E ํ…Œ์ŠคํŠธ, ์ •์  ํ…Œ์ŠคํŠธ๋กœ ๋‚˜๋‰œ๋‹ค.

 

ํ…Œ์ŠคํŠธ ์ข…๋ฅ˜ ์„ค๋ช… ์˜ˆ์‹œ ์ฃผ์š” ๋„๊ตฌ
๋‹จ์œ„ ํ…Œ์ŠคํŠธ(Unit Test) ๊ฐœ๋ณ„ ํ•จ์ˆ˜, ์ปดํฌ๋„ŒํŠธ, ๋ชจ๋“ˆ์˜ ๋™์ž‘ ๊ฒ€์ฆ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ํŠน์ • ํ•จ์ˆ˜ ํ˜ธ์ถœ ์—ฌ๋ถ€ Jest, Vitest, Mocha, Jasmine ๋“ฑ
ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ(Integration Test) ์—ฌ๋Ÿฌ ๋ชจ๋“ˆ์ด ํ•จ๊ป˜ ์ž˜ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธ ์ƒํ’ˆ ๊ตฌ๋งค ์‹œ ์ž”์•ก ์—…๋ฐ์ดํŠธ, ์žฌ๊ณ  ๋ณ€๊ฒฝ Jest, Vitest, React Testing Library ๋“ฑ
E2E ํ…Œ์ŠคํŠธ(End To End Test) ์‚ฌ์šฉ์ž ๊ด€์ ์—์„œ ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ๋ฆ„ ํ…Œ์ŠคํŠธ ๋กœ๊ทธ์ธ๋ถ€ํ„ฐ ์ƒํ’ˆ ๊ตฌ๋งค๊นŒ์ง€์˜ ์ „์ฒด ๊ณผ์ • Cypress, Selenium, Playwright, Puppeteer ๋“ฑ
์ •์  ํ…Œ์ŠคํŠธ(Static Test) ์ฝ”๋“œ ์‹คํ–‰ ์—†์ด ์†Œ์Šค ์ฝ”๋“œ ์ž์ฒด ๋ถ„์„ TypeScript ํƒ€์ž… ๊ฒ€์‚ฌ, ESLint ์ฝ”๋“œ ์Šคํƒ€์ผ ๊ฒ€์ฆ ESLint, Prettier, TypeScript ๋“ฑ

 

์•„๋ž˜์—์„œ Vitest, React Testing Library๋ฅผ ์ด์šฉํ•ด ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž(Vite, React, TypeScript ๊ธฐ๋ฐ˜).

 

Vitest๋Š” Vite ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๋กœ Vite ์„ค์ •๊ณผ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ๋Œ€๋ถ€๋ถ„์˜ Jest API์™€ ํ˜ธํ™˜๋œ๋‹ค. React Testing Library๋Š” ๋ฒ„ํŠผ ํด๋ฆญ, ํ…์ŠคํŠธ ์ž…๋ ฅ ๋“ฑ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‹ค์ œ ์‚ฌ์šฉ์ž์ฒ˜๋Ÿผ ์ƒํ˜ธ์ž‘์šฉํ•˜๋ฉฐ ํ…Œ์ŠคํŠธํ•˜๋„๋ก ์„ค๊ณ„๋œ ๋„๊ตฌ๋‹ค.

 

 

ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ๊ตฌ์ถ•


๐Ÿ’ก ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €๋Š” pnpm์„ ์‚ฌ์šฉํ–ˆ๋‹ค.

 

๊ธฐ๋ณธ ์„ค์ •

โถ ํŒจํ‚ค์ง€ ์„ค์น˜

pnpm i -D vitest @testing-library/react @testing-library/jest-dom jsdom

 

  • vitest : vite ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ ๋Ÿฌ๋„ˆ
  • @testing-library/react : ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ ํŒจํ‚ค์ง€
  • @testing-library/jest-dom : DOM ์š”์†Œ์˜ ํŠน์ • ์ƒํƒœ/์†์„ฑ ํ…Œ์ŠคํŠธ ํŒจํ‚ค์ง€(๋งค์ฒ˜)
  • jsdom : Node์—์„œ ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํŒจํ‚ค์ง€

 

โท vite.config.ts ํŒŒ์ผ ์ˆ˜์ •

/// <reference types="vitest" />
/// <reference types="vite/client" />

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    // describe, it ๋“ฑ ์ „์—ญ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋ฅผ import ์—†์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •
    globals: true,
    // jsdom ํ™˜๊ฒฝ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
    environment: 'jsdom',
    // ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ „ ํŠน์ • ํŒŒ์ผ์ด๋‚˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ์ง€์ •
    setupFiles: ['./src/setup-tests.ts'],
  },
});

 

โธ src ํด๋”์— setup-tests.ts ํŒŒ์ผ ์ƒ์„ฑ

import * as matchers from '@testing-library/jest-dom/matchers';
import { expect } from 'vitest';
import { cleanup } from '@testing-library/react';

/**
 * jest-dom ์—์„œ ์ œ๊ณตํ•˜๋Š” ๋งค์ฒ˜๋ฅผ Vitest expect ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ™•์žฅ
 * ๋งค์ฒ˜๋Š” toBeInTheDocument ๋“ฑ๊ณผ ๊ฐ™์€ DOM ์ƒํƒœ๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ๋ฉ”์„œ๋“œ
 * */
expect.extend(matchers);

afterEach(() => {
  cleanup(); // ๊ฐ ํ…Œ์ŠคํŠธ ์ข…๋ฃŒ ํ›„ DOM ์— ๋ Œ๋”๋ง๋œ ์š”์†Œ ์ œ๊ฑฐ
});

 

โน package.json ์Šคํฌ๋ฆฝํŠธ ์ถ”๊ฐ€ (์„ค์ • ์‹œpnpm test ๋ช…๋ น์–ด๋กœ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๊ฐ€๋Šฅ)

{
  "scripts": {
    // ...
    "test": "vitest"
  }
}

 

โบ tsconfig.app.json ์ˆ˜์ • (Vitest, Testing Library ํƒ€์ž… ์ •์˜ ์ถ”๊ฐ€)

{
  "compilerOptions": {
    "composite": true,
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "types": ["vitest/globals", "@testing-library/jest-dom"] // ์ถ”๊ฐ€

    // ...
  },
  "include": ["src"]
}

 

์œ„์ฒ˜๋Ÿผ ํƒ€์ž… ์ •์˜๋ฅผ ์ถ”๊ฐ€ํ•ด ๋‘๋ฉด TS2582 ์—๋Ÿฌ๊ฐ€ ์‚ฌ๋ผ์ง„๋‹ค.

 

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ

๐Ÿ’ก getByRole ํ•จ์ˆ˜์˜ name ์˜ต์…˜์€ ์ ‘๊ทผ์„ฑ ์ด๋ฆ„์„ ๊ฐ€๋ฆฌํ‚ค๋ฉฐ, ์•„๋ž˜ ๋ฐฉ๋ฒ•์œผ๋กœ ์„ค์ •๋  ์ˆ˜ ์žˆ๋‹ค.

App.test.tsx ํŒŒ์ผ ์ƒ์„ฑ → ์•„๋ž˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ ํ›„ ํ„ฐ๋ฏธ๋„์— pnpm test ๋ฅผ ์ž…๋ ฅํ•ด ๋ณด์ž. 1 passed ํ‘œ์‹œ๊ฐ€ ๋‚˜์˜ค๋ฉด ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ค์ •์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋œ ๊ฒƒ์ด๋‹ค.

// App.test.tsx
import { fireEvent, render, screen } from '@testing-library/react';
import App from './App.tsx';

describe('App component', () => {
  test('increments count when button is clicked', () => {
    render(<App />);

    const button = screen.getByRole('button', { name: /count is \d/i });
    fireEvent.click(button);
    expect(button).toHaveTextContent('count is 1');
  });
});

 

ํ…Œ์ŠคํŠธ ์„ฑ๊ณต ํ™”๋ฉด

์ฝ”๋“œ ์ปค๋ฒ„๋ฆฌ์ง€

์ฝ”๋“œ ์ปค๋ฒ„๋ฆฌ์ง€๋Š” ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ์†Œ์Šค ์ฝ”๋“œ์˜ ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ๋ถ€๋ถ„์„ ์‹คํ–‰ํ–ˆ๋Š”์ง€ ๋ฐฑ๋ถ„์œจ๋กœ ๋‚˜ํƒ€๋‚ธ ์ง€ํ‘œ๋‹ค. ์ฝ”๋“œ ์ปค๋ฒ„๋ฆฌ์ง€๋Š” ์ฃผ๋กœ ์•„๋ž˜ ์ธก์ • ๊ธฐ์ค€์„ ํ†ตํ•ด ํ‰๊ฐ€๋œ๋‹ค.

 

  • Statement Coverage : ๊ฐ ๊ตฌ๋ฌธ์ด ์ตœ์†Œ 1ํšŒ ์ด์ƒ ์‹คํ–‰๋๋Š”์ง€ ์ธก์ •
  • Branch Coverage : ์กฐ๊ฑด๋ฌธ(if/else)์˜ ๊ฐ ๋ถ„๊ธฐ๊ฐ€ ์‹คํ–‰๋๋Š”์ง€ ์ธก์ •
  • Function Coverage : ์ •์˜๋œ ํ•จ์ˆ˜๋“ค์ด ์ตœ์†Œ 1ํšŒ ์ด์ƒ ํ˜ธ์ถœ๋๋Š”์ง€ ์ธก์ •
  • Line Coverage : ๊ฐ ๋ผ์ธ์ด ์ตœ์†Œ 1ํšŒ ์ด์ƒ ์‹คํ–‰๋๋Š”์ง€ ์ธก์ •

 

Vitest์—์„œ๋„ v8(๊ธฐ๋ณธ๊ฐ’) ํ˜น์€ istanbul ๊ณต๊ธ‰์ž๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

โถ v8 ๊ณต๊ธ‰์ž ์„ค์น˜

pnpm i -D @vitest/coverage-v8

 

โท package.json ์Šคํฌ๋ฆฝํŠธ ์ถ”๊ฐ€

{
  "scripts": {
    // ...
    "test": "vitest",
    "coverage": "vitest run --coverage"
  }
}

 

์ด์ œ ํ„ฐ๋ฏธ๋„์— pnpm coverage๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ํ…์ŠคํŠธ ํ˜•์‹์˜ ์ปค๋ฒ„๋ฆฌ์ง€ ํ˜„ํ™ฉ์„ ์ฝ˜์†”์— ์ถœ๋ ฅํ•˜๊ณ , coverage/ ๋””๋ ‰ํ„ฐ๋ฆฌ์— index.html ๊ฐ™์€ ๋ณด๊ณ ์„œ๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ๋‹ค.

 

์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ - ํ„ฐ๋ฏธ๋„ ์ถœ๋ ฅ
์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ - HTML ํŽ˜์ด์ง€

์ปค๋ฒ„๋ฆฌ์ง€ ๊ด€๋ จ ๊ตฌ์„ฑ์€ vite.config.ts ํŒŒ์ผ coverage ์†์„ฑ์—์„œ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํŒจํ–ˆ์„ ๋• ์ปค๋ฒ„๋ฆฌ์ง€ ๋ณด๊ณ ์„œ๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š๋Š”๋ฐ, coverage.reportOnFailure ๊ฐ’์„ true๋กœ ๋ฐ”๊พธ๋ฉด ํ…Œ์ŠคํŠธ ์‹คํŒจ ์‹œ์—๋„ ์ƒ์„ฑ๋œ๋‹ค. ์ด ์™ธ์—๋„ ์ถœ๋ ฅ ๋ฐฉ์‹ ์ง€์ •์„ ์ง€์ •ํ•˜๋Š” coverage.reporter ๋“ฑ ๋‹ค์–‘ํ•œ ์˜ต์…˜์„ ์ œ๊ณตํ•œ๋‹ค.

// vite.config.ts
// ...

export default defineConfig({
  // ...
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./src/setup-tests.ts'],
    coverage: {
      /** ์ปค๋ฒ„๋ฆฌ์ง€ ๊ณต๊ธ‰์ž ์ง€์ •. ๊ธฐ๋ณธ๊ฐ’ v8 */
      provider: 'v8',
      /** ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ฒฐ๊ณผ ์ถœ๋ ฅ ๋ฐฉ์‹ ์ง€์ • ('text' ์ถ”๊ฐ€ํ•˜๋ฉด ํ…์ŠคํŠธ ํ˜•์‹์œผ๋กœ ์ฝ˜์†”์— ์ถœ๋ ฅ) */
      reporter: ['text', 'html'],
      /** ํ…Œ์ŠคํŠธ ์‹คํŒจ ์‹œ ์ปค๋ฒ„๋ฆฌ์ง€ ์ƒ์„ฑ ์—ฌ๋ถ€ */
      reportOnFailure: true,
    },
  },
});

 

Vitest UI

Vitest UI ํŽ˜์ด์ง€

Vitest UI๋Š” ํ…Œ์ŠคํŠธ ํ˜„ํ™ฉ์„ ๋ณด๋‹ค ์‰ฝ๊ฒŒ ํ™•์ธํ•˜๊ณ  ์ƒํ˜ธ์ž‘์šฉ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ๋„๊ตฌ๋‹ค. ์›น UI๋ฅผ ํ†ตํ•ด ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๊ฑฐ๋‚˜ ๊ฒฐ๊ณผ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ณ , ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ํด๋ฆญํ•ด์„œ ์ƒ์„ธํ•œ ์ •๋ณด๋ฅผ ๋ณผ ์ˆ˜๋„ ์žˆ๋‹ค.

 

โถ ํŒจํ‚ค์ง€ ์„ค์น˜

pnpm i -D @vitest/ui

 

โท Vitest UI ์‹คํ–‰ (package.json ํŒŒ์ผ์— test:ui ๋“ฑ ๋ช…๋ น์–ด๋กœ ์ž…๋ ฅํ•ด ๋‘๋ฉด ํŽธํ•˜๋‹ค)

vitest --ui

 

โธ Vitest UI์—์„œ ์ปค๋ฒ„๋ฆฌ์ง€ ํ˜„ํ™ฉ๋„ ๋ณด๊ณ  ์‹ถ๋‹ค๋ฉด vite.config.ts ํŒŒ์ผ์˜ enabled ์†์„ฑ์„ true๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.

// vite.config.ts
// ...

export default defineConfig({
  // ...
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./src/setup-tests.ts'],
    coverage: {
      /** ์ปค๋ฒ„๋ฆฌ์ง€ ๊ธฐ๋Šฅ ํ™œ์„ฑ */
      enabled: true,
      /** ์ปค๋ฒ„๋ฆฌ์ง€ ๊ณต๊ธ‰์ž ์ง€์ •. ๊ธฐ๋ณธ๊ฐ’ v8 */
      provider: 'v8',
      /** ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ฒฐ๊ณผ ์ถœ๋ ฅ ๋ฐฉ์‹ ์ง€์ • ('text' ์ถ”๊ฐ€ํ•˜๋ฉด ํ…์ŠคํŠธ ํ˜•์‹์œผ๋กœ ์ฝ˜์†”์— ์ถœ๋ ฅ) */
      reporter: ['text', 'html'],
      /** ํ…Œ์ŠคํŠธ ์‹คํŒจ ์‹œ ์ปค๋ฒ„๋ฆฌ์ง€ ์ƒ์„ฑ ์—ฌ๋ถ€ */
      reportOnFailure: true,
    },
  },
});

 

์ปค๋ฒ„๋ฆฌ์ง€ ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด Vitest UI ์ƒ๋‹จ์— ์ปค๋ฒ„๋ฆฌ์ง€ ์•„์ด์ฝ˜์ด ์ถ”๊ฐ€๋œ๋‹ค

 

CI ๊ตฌ์ถ•


์ง€์†์  ํ†ตํ•ฉ(CI; Continuous Integration)์€ ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์— ๋Œ€ํ•ด ์ž๋™ํ™”๋œ ๋นŒ๋“œ์™€ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ ํ›„ ๋ฉ”์ธ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์— ์ •๊ธฐ์ ์œผ๋กœ ๋ณ‘ํ•ฉํ•˜๋Š” ๊ฐœ๋ฐœ ๋ฐฉ์‹์„ ๊ฐ€๋ฆฌํ‚จ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ฒ„๊ทธ๋ฅผ ์‹ ์†ํ•˜๊ฒŒ ์ฐพ์•„ ํ•ด๊ฒฐํ•˜๊ณ , ์†Œํ”„ํŠธ์›จ์–ด ๋ฆด๋ฆฌ์ฆˆ ์ฃผ๊ธฐ๋ฅผ ๋‹จ์ถ•์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด PR(Pull Request)์„ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜, ๋ฉ”์ธ ์ €์žฅ์†Œ์— ๋ณ‘ํ•ฉํ•  ๋•Œ๋งˆ๋‹ค ํ…Œ์ŠคํŠธ๋ฅผ ์ž๋™์œผ๋กœ ์‹คํ–‰ํ•˜๋„๋ก ์„ค์ •ํ•ด์„œ ์ฝ”๋“œ ํ’ˆ์งˆ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.

 

GitHub Actions๋ฅผ ์ด์šฉํ•ด ์ƒˆ๋กœ์šด PR์ด ์ƒ์„ฑ๋˜๋ฉด ํƒ€์ž… ์ฒดํฌ, Lint ์ฒดํฌ, ํ…Œ์ŠคํŠธ, ์ปค๋ฒ„๋ฆฌ์ง€ ์š”์•ฝ ๋ณด๊ณ ์„œ ์ฝ”๋ฉ˜ํŠธ๊นŒ์ง€ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ๋Š” ์„ค์ •์„ ํ•ด๋ณด์ž.

 

ํƒ€์ž… / ๋ฆฐํŠธ ์ฒดํฌ

๋จผ์ € .github/workflows ํด๋”์— ci.yaml ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค. GitHub Actions ์›Œํฌํ”Œ๋กœ์šฐ๋Š” yaml ํŒŒ์ผ์„ ํ†ตํ•ด์„œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, yaml ํŒŒ์ผ์€ ํ•ญ์ƒ workflows ํด๋”์— ์œ„์น˜ํ•ด ์žˆ์–ด์•ผ ํ•œ๋‹ค.

๐Ÿ“ฆ Project Root
โ””โ”€ .github
   โ””โ”€ workflows
      โ””โ”€ ci.yaml

 

๊ทธ ํ›„ ์•„๋ž˜ ๋‚ด์šฉ์„ ci.yaml ํŒŒ์ผ์— ๋ถ™์—ฌ ๋„ฃ๋Š”๋‹ค. ์›Œํฌํ”Œ๋กœ์šฐ์˜ ๊ฐ ์ž‘์—…(Job)์€ ๋…๋ฆฝ์ ์œผ๋กœ (๋ณ‘๋ ฌ) ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋งค๋ฒˆ node, pnpm ์„ค์น˜๊ฐ€ ํ•„์š”ํ•˜์ง€๋งŒ, ์บ์‹œ ๊ธฐ๋Šฅ์„ ์ด์šฉํ•ด ๊ธฐ์กด ์„ค์น˜ํ–ˆ๋˜ ์˜์กด์„ฑ ๋ชจ๋“ˆ์„ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

# ci.yaml
name: CI

on:
  push:
    branches: [main] # main ๋ธŒ๋žœ์น˜์— pushํ•˜๋ฉด ์›Œํฌํ”Œ๋กœ์šฐ ์‹คํ–‰
  pull_request:
    branches: [main] # main ๋ธŒ๋žœ์น˜๋กœ Pull Request๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ์›Œํฌํ”Œ๋กœ์šฐ ์‹คํ–‰
  workflow_dispatch: # ์ˆ˜๋™ ํŠธ๋ฆฌ๊ฑฐ ๊ธฐ๋Šฅ ํ™œ์„ฑ

jobs:
  # ์ฒซ ๋ฒˆ์งธ Job
  lint:
    runs-on: ubuntu-latest
    steps:
      # ์›Œํฌํ”Œ๋กœ์šฐ ์‹คํ–‰ ์ค‘ ์ €์žฅ์†Œ ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์™€์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์•ก์…˜
      - name: Checkout repository
        uses: actions/checkout@v4

      # pnpm ์„ค์น˜ ์•ก์…˜. ์บ์‹œ ์‚ฌ์šฉ์„ ์œ„ํ•ด actions/setup-node@v4 ๋ณด๋‹ค ๋จผ์ € ์„ค์น˜
      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: latest
          run_install: false

      # Node.js๋ฅผ ์„ค์น˜ํ•˜๊ณ  pnpm ์˜์กด์„ฑ ์บ์‹œ ํ™œ์„ฑํ™”
      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: 'pnpm'

      # ํ”„๋กœ์ ํŠธ ์˜์กด์„ฑ ์„ค์น˜
      - name: Install dependencies
        run: pnpm install

      # ESLint ์‹คํ–‰
      - name: Run ESLint
        run: pnpm lint

  # ๋‘ ๋ฒˆ์งธ Job
  type-check:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: latest
          run_install: false

      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install

      # ํƒ€์ž… ์ฒดํฌ ์‹คํ–‰
      - name: Type check
        run: pnpm type-check

 

๐Ÿ’ก yaml ํŒŒ์ผ์—์„œ with ํ‚ค์›Œ๋“œ๋Š” ์ฃผ๋กœ ์•ก์…˜์˜ ์ž…๋ ฅ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ง€์ •ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. with๋Š” uses ํ‚ค์›Œ๋“œ ๋‹ค์Œ์— ์œ„์น˜ํ•˜๋ฉฐ, with ์•„๋ž˜์—๋Š” ํ‚ค-๊ฐ’ ์Œ ํ˜•ํƒœ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ง€์ •ํ•œ๋‹ค. with๋กœ ์ง€์ •๋œ ์ž…๋ ฅ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์•ก์…˜ ์‹คํ–‰ ์‹œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๋ณ€ํ™˜๋œ๋‹ค.

 

ํ…Œ์ŠคํŠธ / ์ปค๋ฒ„๋ฆฌ์ง€

ํ…Œ์ŠคํŠธ์™€ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ์ฒดํฌํ•˜๊ธฐ ์œ„ํ•ด test-and-coverage ์ด๋ฆ„์˜ ์ž‘์—…(Job)์„ ์•„๋ž˜์ฒ˜๋Ÿผ ์ถ”๊ฐ€ํ•œ๋‹ค. needs ์†์„ฑ์€ ์ž‘์—… ๊ฐ„ ์˜์กด์„ฑ์„ ์ •์˜ํ•ด์„œ ์‹คํ–‰ ์ˆœ์„œ๋ฅผ ์ œ์–ดํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, lint, type-check ์ž‘์—…์„ ์™„๋ฃŒํ–ˆ์„ ๋•Œ๋งŒ test-and-coverage ์ž‘์—…์„ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€ํ–ˆ๋‹ค.

# ci.yaml
name: CI

on:
  # ... ์ƒ๋žต

jobs:
  lint:
    # ... ์ƒ๋žต

  type-check:
    # ... ์ƒ๋žต

  test-and-coverage:
    # lint, type-check ์ž‘์—…์„ ์™„๋ฃŒํ•ด์•ผ๋งŒ ์‹คํ–‰
    needs: [lint, type-check]
    runs-on: ubuntu-latest

    steps:
      # checkout, node/pnpm/์˜์กด์„ฑ ์„ค์น˜ step ์ฝ”๋“œ ์ƒ๋žต

      - name: Run tests and coverage
        # package.json - scripts ์†์„ฑ์— ์ถ”๊ฐ€ํ•œ coverage ๋ช…๋ น์–ด ์‹คํ–‰
        run: pnpm coverage

 

์ด์ œ PR์„ ์ƒ์„ฑํ•˜๊ณ  Actions ๋ฉ”๋‰ด๋กœ ๋“ค์–ด๊ฐ€ ๋ณด๋ฉด ํ…Œ์ŠคํŠธ์™€ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ฒ€์‚ฌ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

ํ…Œ์ŠคํŠธ/์ปค๋ฒ„๋ฆฌ์ง€ ๊ฒฐ๊ณผ ์ถœ๋ ฅ ํ™”๋ฉด

ํ…Œ์ŠคํŠธ ๋ณด๊ณ ์„œ ์ฝ”๋ฉ˜ํŠธ / ์–ด๋…ธํ…Œ์ด์…˜

publish-unit-test-result-action ์•ก์…˜์„ ์ด์šฉํ•ด์„œ ํ…Œ์ŠคํŠธ ๋ณด๊ณ ์„œ๋ฅผ PR ์ฝ”๋ฉ˜ํŠธ๋กœ ๋‚จ๊ธฐ๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

ํ…Œ์ŠคํŠธ ๋ณด๊ณ ์„œ ์ฝ”๋ฉ˜ํŠธ
Files-changed ์–ด๋…ธํ…Œ์ด์…˜

์ด ์•ก์…˜์—์„œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” JUnit ํ˜•์‹๋งŒ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— vite.config.ts ๊ตฌ์„ฑ ํŒŒ์ผ์„ ์•„๋ž˜์ฒ˜๋Ÿผ ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค. reporters ์†์„ฑ์—์„œ default๋Š” ํ„ฐ๋ฏธ๋„ ์ถœ๋ ฅ ํ˜•์‹์„ ์˜๋ฏธํ•œ๋‹ค. PR ํŽ˜์ด์ง€ Files-changed ํƒญ์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํŒจํ•œ ์ฝ”๋“œ์— ์–ด๋…ธํ…Œ์ด์…˜์„ ํ‘œ์‹œํ•˜๋ ค๋ฉด github-actions๋„ ์ถ”๊ฐ€ํ•ด ๋‘”๋‹ค. ์›Œํฌํ”Œ๋กœ์šฐ๊ฐ€ ์‹คํ–‰์ค‘์ผ ๋• GITHUB_ACTIONS ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ true๋กœ ์„ค์ •๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์ด์šฉํ•ด reporters ๊ฐ’์„ ์กฐ๊ฑด๋ถ€๋กœ ์ง€์ •ํ–ˆ๋‹ค.

// vite.config.ts
// ...

export default defineConfig({
  // ...
  test: {
    // ...
    /** ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๊ฒฐ๊ณผ ๋ฆฌํฌํŠธ ํ˜•์‹ ์ง€์ • */
    reporters: process.env.GITHUB_ACTIONS
      ? ['default', 'junit', 'github-actions']
      : ['default', 'junit'],
    /** ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ํŒŒ์ผ ์ด๋ฆ„ ์ง€์ • */
    outputFile: 'test-results.xml',
    // ...
  },
});

 

์•ก์…˜ ์‹คํ–‰์„ ์œ„ํ•ด ci.yaml ํŒŒ์ผ test-and-coverage ์ž‘์—…์˜ ๊ถŒํ•œ(permissions)์„ ์•„๋ž˜์ฒ˜๋Ÿผ ์ˆ˜์ •ํ•œ๋‹ค.

# ci.yaml
name: CI

on:
  # ...์ƒ๋žต

jobs:
  lint:
    # ...์ƒ๋žต

  type-check:
    # ...์ƒ๋žต

  test-and-coverage:
    needs: [lint, type-check]
    runs-on: ubuntu-latest

    # (์ถ”๊ฐ€) ๋น„๊ณต๊ฐœ ์ €์žฅ์†Œ๋Š” ์•„๋ž˜ 4๊ฐœ ๊ถŒํ•œ ํ•„์š”. ๊ณต๊ฐœ ์ €์žฅ์†Œ๋Š” checks, pull-requests ๊ถŒํ•œ ํ•„์š”
    permissions:
      contents: read
      pull-requests: write
      issues: read
      checks: write

    steps:
      # checkout, node/pnpm/์˜์กด์„ฑ ์„ค์น˜ step ์ฝ”๋“œ ์ƒ๋žต

      - name: Run tests and coverage
        run: pnpm coverage

      # (์ถ”๊ฐ€)
      - name: Publish Test Results
        uses: EnricoMi/publish-unit-test-result-action@v2
        # ์ด์ „ step ์„ฑ๊ณต ์—ฌ๋ถ€์™€ ์ƒ๊ด€์—†์ด ์ด step ํ•ญ์ƒ ์‹คํ–‰
        if: always()
        with:
          # vite.config.ts - test.outputFile ์†์„ฑ์— ์„ค์ •ํ•œ ํŒŒ์ผ ์ด๋ฆ„
          files: test-results.xml
          # ๋ณด๊ณ ์„œ ํ—ค๋” ์ด๋ฆ„ ์ง€์ •
          check_name: Unit Test Report

 

์ปค๋ฒ„๋ฆฌ์ง€ ๋ณด๊ณ ์„œ ์ฝ”๋ฉ˜ํŠธ

vitest-coverage-report-action ์•ก์…˜์„ ์ด์šฉํ•ด์„œ ์ปค๋ฒ„๋ฆฌ์ง€ ๋ณด๊ณ ์„œ๋ฅผ PR ์ฝ”๋ฉ˜ํŠธ๋กœ ๋‚จ๊ธฐ๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ปค๋ฒ„๋ฆฌ์ง€ ๋ณด๊ณ ์„œ ์ฝ”๋ฉ˜ํŠธ

์ปค๋ฒ„๋ฆฌ์ง€ ๋ณด๊ณ ์„œ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด vite.config.ts ํŒŒ์ผ์„ ์•„๋ž˜์ฒ˜๋Ÿผ ์ˆ˜์ •ํ•œ๋‹ค. ์ด ์•ก์…˜์€ json-summary ๋ฆฌํฌํ„ฐ(reporter)๊ฐ€ ํ•„์ˆ˜๋กœ ํฌํ•จ๋˜์–ด์•ผ ํ•˜๋ฉฐ, File Coverage๋„ ์ƒ์„ฑํ•˜๋ ค๋ฉด json ๋ฆฌํฌํ„ฐ๋„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค. ์ปค๋ฒ„๋ฆฌ์ง€ ๋ณด๊ณ ์„œ์˜ Status ํ•ญ๋ชฉ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํŒŒ๋ž€์ƒ‰ ๐Ÿ”ต ์›์œผ๋กœ ๋‚˜์˜ค๋Š”๋ฐ, ์ž„๊ณ„๊ฐ’(thresholds)์„ ์„ค์ •ํ•ด ๋‘๋ฉด ์ž„๊ณ„๊ฐ’์„ ์ถฉ์กฑํ•˜์ง€ ์•Š์•˜์„ ๋•Œ ๋นจ๊ฐ„์ƒ‰ ๐Ÿ”ด์›์œผ๋กœ ํ‘œ์‹œํ•ด ์ค€๋‹ค.

// vite.config.ts
// ...

export default defineConfig({
  // ...
  test: {
    // ...
    coverage: {
      // ...
      /** ์ปค๋ฒ„๋ฆฌ์ง€ ๊ธฐ๋Šฅ ํ™œ์„ฑํ™” */
      enabled: true,
      /** ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ฒฐ๊ณผ ์ถœ๋ ฅ ๋ฐฉ์‹ ์ง€์ • */
      reporter: ['text', 'html', 'json', 'json-summary'],
      /** ํ…Œ์ŠคํŠธ ์‹คํŒจ ์‹œ ์ปค๋ฒ„๋ฆฌ์ง€ ์ƒ์„ฑ ์—ฌ๋ถ€ */
      reportOnFailure: true,
      /** ์ปค๋ฒ„๋ฆฌ์ง€ ์ž„๊ณ„๊ฐ’ ์„ค์ • */
      thresholds: { lines: 70, branches: 70, functions: 70, statements: 70 },
    },
  },
});

 

๊ทธ ํ›„ ci.yaml ํŒŒ์ผ์— Report Coverage ์Šคํ…์„ ์ถ”๊ฐ€ํ•œ๋‹ค. if: always() ์†์„ฑ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™ํ•˜๋ ค๋ฉด(ํ•ญ์ƒ ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ ์ƒ์„ฑ) vite.config.ts ํŒŒ์ผ์˜ reportOnFailure ์†์„ฑ์„ true๋กœ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค.

# ci.yaml
name: CI

on:
  # ...์ƒ๋žต

jobs:
  lint:
    # ...์ƒ๋žต

  type-check:
    # ...์ƒ๋žต

  test-and-coverage:
    needs: [lint, type-check]
    runs-on: ubuntu-latest

    permissions:
      contents: read
      pull-requests: write
      issues: read
      checks: write

    steps:
      # checkout, node/pnpm/์˜์กด์„ฑ ์„ค์น˜ step ์ฝ”๋“œ ์ƒ๋žต

      - name: Run tests and coverage
        run: pnpm coverage

      - name: Publish Test Results
        uses: EnricoMi/publish-unit-test-result-action@v2
        if: always()
        with:
          files: test-results.xml
          check_name: Unit Test Report

      # (์ถ”๊ฐ€)
      - name: Report Coverage
        # ์ด์ „ step ๊ฒฐ๊ณผ์— ์ƒ๊ด€์—†์ด ์ด step ํ•ญ์ƒ ์‹คํ–‰ (ํ•ญ์ƒ ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ ์ƒ์„ฑ)
        # vite.config.ts ํŒŒ์ผ์—์„œ reportOnFailure: true๋กœ ์„ค์ • ํ•„์š”
        if: always()
        uses: davelosert/vitest-coverage-report-action@v2

 

 

์ตœ์ข… ์ฝ”๋“œ


๋”๋ณด๊ธฐ
name: CI

on:
  push:
    branches: [main] # main ๋ธŒ๋žœ์น˜์— push ํ•˜๋ฉด ์›Œํฌํ”Œ๋กœ์šฐ ์‹คํ–‰
  pull_request:
    branches: [main] # main ๋ธŒ๋žœ์น˜๋กœ Pull Request ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ์›Œํฌํ”Œ๋กœ์šฐ ์‹คํ–‰
  workflow_dispatch: # ์ˆ˜๋™ ํŠธ๋ฆฌ๊ฑฐ ๊ธฐ๋Šฅ ํ™œ์„ฑ

jobs:
  lint:
    runs-on: ubuntu-latest

    steps:
      # ์›Œํฌํ”Œ๋กœ์šฐ ์‹คํ–‰ ์ค‘ ์ €์žฅ์†Œ ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์™€์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์•ก์…˜
      - name: Checkout repository
        uses: actions/checkout@v4

      # pnpm ์„ค์น˜ ์•ก์…˜. ์บ์‹œ ์‚ฌ์šฉ์„ ์œ„ํ•ด actions/setup-node@v4 ๋ณด๋‹ค ๋จผ์ € ์„ค์น˜
      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: latest
          run_install: false

      # Node.js๋ฅผ ์„ค์น˜ํ•˜๊ณ  pnpm ์˜์กด์„ฑ ์บ์‹œ ํ™œ์„ฑํ™”
      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: 'pnpm'

      # ํ”„๋กœ์ ํŠธ ์˜์กด์„ฑ ์„ค์น˜
      - name: Install dependencies
        run: pnpm install

      - name: Run ESLint
        run: pnpm lint

  type-check:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: latest
          run_install: false

      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install

      - name: Type check
        run: pnpm type-check

  test-and-coverage:
    # lint, type-check ์ž‘์—…์„ ์™„๋ฃŒํ•ด์•ผ๋งŒ ์‹คํ–‰
    needs: [lint, type-check]
    runs-on: ubuntu-latest

    permissions:
      # publish-unit-test-result-action ์•ก์…˜์—์„œ private ๋ ˆํฌ์ง€ํ† ๋ฆฌ๋Š” ์•„๋ž˜ 4๊ฐœ ๊ถŒํ•œ ํ•„์š”
      # public ๋ ˆํฌ์ง€ํ† ๋ฆฌ๋Š” checks, pull-requests 2๊ฐœ ๊ถŒํ•œ ํ•„์š”
      contents: read
      pull-requests: write
      issues: read
      checks: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: latest
          run_install: false

      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install

      - name: Run tests and coverage
        run: pnpm coverage

      - name: Publish Test Results
        uses: EnricoMi/publish-unit-test-result-action@v2
        # ์ด์ „ step ์„ฑ๊ณต ์—ฌ๋ถ€์™€ ์ƒ๊ด€์—†์ด ์ด step ํ•ญ์ƒ ์‹คํ–‰
        if: always()
        with:
          # vite.config.ts - test.outputFile ์†์„ฑ์— ์„ค์ •ํ•œ ํŒŒ์ผ ์ด๋ฆ„
          files: test-results.xml
          # ๋ณด๊ณ ์„œ ํ—ค๋” ์ด๋ฆ„ ์ง€์ •
          check_name: Unit Test Report

      - name: Report Coverage
        # ์ด์ „ step ๊ฒฐ๊ณผ์— ์ƒ๊ด€์—†์ด ์ด step ํ•ญ์ƒ ์‹คํ–‰ (ํ•ญ์ƒ ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ ์ƒ์„ฑ)
        # vite.config.ts ํŒŒ์ผ์—์„œ reportOnFailure: true๋กœ ์„ค์ • ํ•„์š”
        if: always()
        uses: davelosert/vitest-coverage-report-action@v2
๋”๋ณด๊ธฐ
/// <reference types="vitest" />
/// <reference types="vite/client" />

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
import svgr from 'vite-plugin-svgr';
import { coverageConfigDefaults } from 'vitest/config';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    tsconfigPaths(),
    svgr({
      // svgr options: https://react-svgr.com/docs/options/
      svgrOptions: {
        exportType: 'default',
        ref: true,
        svgo: false,
        titleProp: true,
      },
      include: '**/*.svg',
    }),
  ],
  resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'] },
  server: { port: 3000, open: true },
  test: {
    /** describe, it ๋“ฑ ์ „์—ญ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋ฅผ import ์—†์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ • */
    globals: true,
    /** jsdom ํ™˜๊ฒฝ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ */
    environment: 'jsdom',
    /** ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ „ ํŠน์ • ํŒŒ์ผ์ด๋‚˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ์ง€์ • */
    setupFiles: ['./src/setup-tests.ts'],
    /**
     * ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๊ฒฐ๊ณผ ๋ฆฌํฌํŠธ ํ˜•์‹ ์ง€์ •
     *
     * GitHub Actions ์›Œํฌํ”Œ๋กœ์šฐ๊ฐ€ ์‹คํ–‰์ค‘์ด๋ฉด GITHUB_ACTIONS ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋Š” true ๋กœ ์„ค์ •๋จ
     * @see https://docs.github.com/ko/actions/learn-github-actions/variables#default-environment-variables
     *
     * publish-unit-test-result-action ์•ก์…˜์—์„œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” JUnit ๋งŒ ์ง€์›ํ•ด์„œ 'junit' ์ถ”๊ฐ€
     * 'default' ๋ ˆํฌํ„ฐ๋Š” ํ„ฐ๋ฏธ๋„ ์ถœ๋ ฅ์„ ์˜๋ฏธ
     * 'github-actions' ์ถ”๊ฐ€ํ•˜๋ฉด PR - Files-changed ํƒญ์—์„œ ์–ด๋…ธํ…Œ์ด์…˜ ํ™•์ธ ๊ฐ€๋Šฅ
     * @see https://vitest.dev/guide/reporters.html#github-actions-reporter
     * */
    reporters: process.env.GITHUB_ACTIONS
      ? ['default', 'junit', 'github-actions']
      : ['default', 'junit'],
    /** ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ํŒŒ์ผ ์ด๋ฆ„ ์ง€์ • */
    outputFile: 'test-results.xml',
    include: ['**/*.test.{ts,tsx,js,jsx}'],
    coverage: {
      /** ์ปค๋ฒ„๋ฆฌ์ง€ ๊ธฐ๋Šฅ ํ™œ์„ฑํ™” */
      enabled: true,
      /** ์ปค๋ฒ„๋ฆฌ์ง€ ๊ณต๊ธ‰์ž ์ง€์ •. ๊ธฐ๋ณธ๊ฐ’ v8 */
      provider: 'v8',
      exclude: [
        ...coverageConfigDefaults.exclude,
        '**/index.ts',
        '**/*.config.{js,ts}',
        'src/{main,app}.tsx',
      ],
      /**
       * ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ฒฐ๊ณผ ์ถœ๋ ฅ ๋ฐฉ์‹ ์ง€์ • ('text' ์ถ”๊ฐ€ํ•˜๋ฉด ํ…์ŠคํŠธ ํ˜•์‹์œผ๋กœ ์ฝ˜์†”์— ์ถœ๋ ฅ)
       * vitest-coverage-report-action ์•ก์…˜ ์‚ฌ์šฉํ•˜๋ ค๋ฉด 'json', 'json-summary' ์ถ”๊ฐ€ ํ•„์š”
       * @see https://github.com/davelosert/vitest-coverage-report-action#usage
       * */
      reporter: ['text', 'html', 'json', 'json-summary'],
      /** ํ…Œ์ŠคํŠธ ์‹คํŒจ ์‹œ ์ปค๋ฒ„๋ฆฌ์ง€ ์ƒ์„ฑ ์—ฌ๋ถ€ */
      reportOnFailure: true,
      /**
       * ์ปค๋ฒ„๋ฆฌ์ง€ ์ž„๊ณ„๊ฐ’ ์„ค์ •
       * @see https://github.com/davelosert/vitest-coverage-report-action#coverage-thresholds */
      thresholds: {
        lines: 70,
        branches: 70,
        functions: 70,
        statements: 70,
      },
    },
  },
});

 

 

๋ ˆํผ๋Ÿฐ์Šค


 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•