Skip to content

Latest commit

ย 

History

History
460 lines (344 loc) ยท 12.1 KB

File metadata and controls

460 lines (344 loc) ยท 12.1 KB

ํ…Œ์ŠคํŠธ

์ด ๋ฌธ์„œ์—์„œ๋Š” Altsis ํ”„๋กœ์ ํŠธ์˜ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๋ฐฉ๋ฒ•, ์ž‘์„ฑ ๊ฐ€์ด๋“œ, ๋””๋ฒ„๊น… ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.


๋ชฉ์ฐจ

  1. ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ
  2. ํ…Œ์ŠคํŠธ ์‹คํ–‰
  3. ๋ฐฑ์—”๋“œ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ
  4. ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ
  5. ํ…Œ์ŠคํŠธ ๋””๋ฒ„๊น…
  6. ํ…Œ์ŠคํŠธ ๋ชจ๋ฒ” ์‚ฌ๋ก€

1. ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ

์˜์—ญ ํ”„๋ ˆ์ž„์›Œํฌ ์ถ”๊ฐ€ ๋„๊ตฌ
๋ฐฑ์—”๋“œ Jest 29.x node-mocks-http (Express req/res ๋ชจํ‚น), babel-jest
ํ”„๋ก ํŠธ์—”๋“œ Jest (react-scripts) @testing-library/react, @testing-library/jest-dom, @testing-library/user-event

๋ฐฑ์—”๋“œ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ

๋ฐฑ์—”๋“œ ํ…Œ์ŠคํŠธ๋Š” Babel์„ ํ†ตํ•ด ES Module์„ CommonJS๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค:

  • jest.config.json - Jest ์„ค์ •
  • @babel/preset-env + @babel/plugin-transform-modules-commonjs - ES Module ๋ณ€ํ™˜
  • NODE_ENV=test ์‹œ .env.test ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ

ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ

ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ๋Š” react-scripts test๋ฅผ ํ†ตํ•ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค:

  • Create React App ๋‚ด์žฅ Jest ์„ค์ • ์‚ฌ์šฉ
  • @testing-library/react - React ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ ์œ ํ‹ธ๋ฆฌํ‹ฐ
  • @testing-library/jest-dom - DOM ๋งค์ฒ˜ ํ™•์žฅ

2. ํ…Œ์ŠคํŠธ ์‹คํ–‰

๋ฐฑ์—”๋“œ ํ…Œ์ŠคํŠธ

cd backend

# ์ „์ฒด ํ…Œ์ŠคํŠธ ์‹คํ–‰
yarn test

# ํŠน์ • ํ…Œ์ŠคํŠธ ํŒŒ์ผ๋งŒ ์‹คํ–‰
yarn test -- --testPathPattern="seasons"

# ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ ํ…Œ์ŠคํŠธ
yarn test --prod

๋ฐฑ์—”๋“œ yarn test ๋ช…๋ น์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค:

jest -c ./jest.config.json --detectOpenHandles --forceExit
  • --detectOpenHandles: ์—ด๋ ค ์žˆ๋Š” ํ•ธ๋“ค(DB ์—ฐ๊ฒฐ ๋“ฑ) ๊ฐ์ง€
  • --forceExit: ํ…Œ์ŠคํŠธ ์™„๋ฃŒ ํ›„ ๊ฐ•์ œ ์ข…๋ฃŒ (๋น„๋™๊ธฐ ํ•ธ๋“ค ์ •๋ฆฌ)

ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ

cd frontend

# ์ „์ฒด ํ…Œ์ŠคํŠธ ์‹คํ–‰
yarn test

# ํŠน์ • ํ…Œ์ŠคํŠธ๋งŒ ๊ฐ์‹œ ๋ชจ๋“œ๋กœ ์‹คํ–‰
yarn test --watch TestName

# ์ปค๋ฒ„๋ฆฌ์ง€ ํฌํ•จ ์‹คํ–‰
yarn test -- --coverage

ํ…Œ์ŠคํŠธ ๋ช…๋ น์–ด ์š”์•ฝ

์œ„์น˜ ๋ช…๋ น์–ด ์„ค๋ช…
backend/ yarn test ์ „์ฒด ๋ฐฑ์—”๋“œ ํ…Œ์ŠคํŠธ (Jest)
backend/ yarn test --prod ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ ํ…Œ์ŠคํŠธ
frontend/ yarn test ์ „์ฒด ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ
frontend/ yarn test --watch TestName ํŠน์ • ํ…Œ์ŠคํŠธ ๊ฐ์‹œ ๋ชจ๋“œ

3. ๋ฐฑ์—”๋“œ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

์ปจํŠธ๋กค๋Ÿฌ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ

node-mocks-http๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Express์˜ req/res ๊ฐ์ฒด๋ฅผ ๋ชจํ‚นํ•ฉ๋‹ˆ๋‹ค:

import httpMocks from "node-mocks-http";
import { create } from "../controllers/seasons.js";

describe("SeasonController", () => {
  describe("create (CSeason)", () => {
    it("ํ•„์ˆ˜ ํ•„๋“œ๊ฐ€ ์—†์œผ๋ฉด 400์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค", async () => {
      const req = httpMocks.createRequest({
        method: "POST",
        url: "/api/seasons",
        body: {
          // school ํ•„๋“œ ๋ˆ„๋ฝ
          year: "2024",
          term: "1ํ•™๊ธฐ",
        },
        user: {
          academyId: "testAcademy",
          auth: "admin",
        },
      });
      const res = httpMocks.createResponse();

      await create(req, res);

      expect(res.statusCode).toBe(400);
      expect(res._getJSONData().message).toBe("SCHOOL_REQUIRED");
    });

    it("์œ ํšจํ•œ ์š”์ฒญ์ด๋ฉด 200๊ณผ season ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค", async () => {
      const req = httpMocks.createRequest({
        method: "POST",
        url: "/api/seasons",
        body: {
          school: "64a1b2c3d4e5f6g7h8i9j0k1",
          year: "2024",
          term: "1ํ•™๊ธฐ",
          period: { start: "2024-03-01", end: "2024-07-31" },
        },
        user: {
          academyId: "testAcademy",
          auth: "admin",
        },
      });
      const res = httpMocks.createResponse();

      await create(req, res);

      expect(res.statusCode).toBe(200);
      const data = res._getJSONData();
      expect(data.season).toBeDefined();
      expect(data.season.year).toBe("2024");
    });
  });
});

๋ชจ๋ธ ํ…Œ์ŠคํŠธ

import mongoose from "mongoose";
import { Season } from "../models/Season.js";

describe("Season Model", () => {
  let connection;

  beforeAll(async () => {
    // ํ…Œ์ŠคํŠธ์šฉ DB ์—ฐ๊ฒฐ
    connection = await mongoose.createConnection(process.env.TEST_DB_URL);
  });

  afterAll(async () => {
    await connection.close();
  });

  it("ํ•„์ˆ˜ ํ•„๋“œ๊ฐ€ ์žˆ์œผ๋ฉด Season์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค", async () => {
    const seasonData = {
      school: new mongoose.Types.ObjectId(),
      schoolId: "testSchool",
      schoolName: "ํ…Œ์ŠคํŠธ ํ•™๊ต",
      year: "2024",
      term: "1ํ•™๊ธฐ",
    };

    const SeasonModel = connection.model("Season", Season.schema);
    const season = new SeasonModel(seasonData);
    const savedSeason = await season.save();

    expect(savedSeason._id).toBeDefined();
    expect(savedSeason.year).toBe("2024");
    expect(savedSeason.isActivated).toBe(false); // ๊ธฐ๋ณธ๊ฐ’
  });
});

์„œ๋น„์Šค ํ…Œ์ŠคํŠธ

import { SeasonService } from "../services/seasons.js";

describe("SeasonService", () => {
  it("ํ•™๊ธฐ ํ™œ์„ฑํ™” ์‹œ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•œ๋‹ค", async () => {
    // Given
    const dbName = "testAcademy";
    const seasonId = "64a1b2c3d4e5f6g7h8i9j0k1";

    // When
    const result = await SeasonService.onActivate(dbName, seasonId);

    // Then
    expect(result.isActivated).toBe(true);
  });
});

4. ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ํ…Œ์ŠคํŠธ

import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import MyComponent from "./MyComponent";

describe("MyComponent", () => {
  it("์ œ๋ชฉ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ Œ๋”๋ง๋˜์–ด์•ผ ํ•œ๋‹ค", () => {
    render(<MyComponent title="ํ…Œ์ŠคํŠธ ์ œ๋ชฉ" />);

    expect(screen.getByText("ํ…Œ์ŠคํŠธ ์ œ๋ชฉ")).toBeInTheDocument();
  });

  it("๋ฐ์ดํ„ฐ๊ฐ€ ์—†์„ ๋•Œ ๋นˆ ์ƒํƒœ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•ด์•ผ ํ•œ๋‹ค", () => {
    render(<MyComponent title="ํ…Œ์ŠคํŠธ" data={[]} />);

    expect(screen.getByText("๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")).toBeInTheDocument();
  });
});

์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ ํ…Œ์ŠคํŠธ

import { render, screen, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import MyComponent from "./MyComponent";

describe("MyComponent ์ƒํ˜ธ์ž‘์šฉ", () => {
  it("๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•œ๋‹ค", async () => {
    const handleClick = jest.fn();
    render(<MyComponent onClick={handleClick} />);

    await userEvent.click(screen.getByRole("button", { name: "ํ™•์ธ" }));

    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it("์ž…๋ ฅ๊ฐ’ ๋ณ€๊ฒฝ ์‹œ ์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์–ด์•ผ ํ•œ๋‹ค", async () => {
    render(<MyComponent />);

    const input = screen.getByPlaceholderText("์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”");
    await userEvent.type(input, "ํ™๊ธธ๋™");

    expect(input).toHaveValue("ํ™๊ธธ๋™");
  });
});

๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ

import { render, screen, waitFor } from "@testing-library/react";
import MyComponent from "./MyComponent";

describe("MyComponent ๋น„๋™๊ธฐ", () => {
  it("๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ํ›„ ๋ชฉ๋ก์ด ํ‘œ์‹œ๋˜์–ด์•ผ ํ•œ๋‹ค", async () => {
    render(<MyComponent />);

    // ๋กœ๋”ฉ ์ƒํƒœ ํ™•์ธ
    expect(screen.getByText("๋กœ๋”ฉ ์ค‘...")).toBeInTheDocument();

    // ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์™„๋ฃŒ ๋Œ€๊ธฐ
    await waitFor(() => {
      expect(screen.getByText("ํ•ญ๋ชฉ 1")).toBeInTheDocument();
    });

    expect(screen.queryByText("๋กœ๋”ฉ ์ค‘...")).not.toBeInTheDocument();
  });
});

5. ํ…Œ์ŠคํŠธ ๋””๋ฒ„๊น…

Chrome Inspector๋ฅผ ์ด์šฉํ•œ ๋””๋ฒ„๊น…

Node.js์˜ --inspect ํ”Œ๋ž˜๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Chrome DevTools์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ๋””๋ฒ„๊น…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹คํ–‰ ๋ฐฉ๋ฒ•

cd frontend

# ๋””๋ฒ„๊ฑฐ ์—ฐ๋™ ํ…Œ์ŠคํŠธ (๊ฐ์‹œ ๋ชจ๋“œ)
yarn debug-test --watch TestName

Chrome DevTools ์—ฐ๊ฒฐ

  1. ์œ„ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  2. Chrome ๋ธŒ๋ผ์šฐ์ €์—์„œ chrome://inspect๋ฅผ ์—ฝ๋‹ˆ๋‹ค.
  3. "Remote Target" ์„น์…˜์—์„œ Node.js ํ”„๋กœ์„ธ์Šค๋ฅผ ์ฐพ์•„ "inspect"๋ฅผ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.
  4. DevTools๊ฐ€ ์—ด๋ฆฌ๋ฉด Sources ํƒญ์—์„œ ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฝ”๋“œ ๋‚ด ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์— debugger ๋ฌธ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ํ•ด๋‹น ์ง€์ ์—์„œ ์‹คํ–‰์ด ๋ฉˆ์ถฅ๋‹ˆ๋‹ค:

it("๋””๋ฒ„๊น…์ด ํ•„์š”ํ•œ ํ…Œ์ŠคํŠธ", async () => {
  const result = await someFunction();
  debugger; // ์—ฌ๊ธฐ์„œ ์‹คํ–‰์ด ๋ฉˆ์ถค
  expect(result).toBe(expected);
});

VS Code ๋””๋ฒ„๊ฑฐ

VS Code์—์„œ ์ง์ ‘ ํ…Œ์ŠคํŠธ๋ฅผ ๋””๋ฒ„๊น…ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. .vscode/launch.json์— ๋‹ค์Œ ์„ค์ •์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Jest Tests (Backend)",
      "type": "node",
      "request": "launch",
      "runtimeExecutable": "yarn",
      "runtimeArgs": ["test", "--runInBand"],
      "cwd": "${workspaceFolder}/backend",
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    },
    {
      "name": "Debug Jest Tests (Frontend)",
      "type": "node",
      "request": "launch",
      "runtimeExecutable": "yarn",
      "runtimeArgs": [
        "test",
        "--runInBand",
        "--no-cache",
        "--watchAll=false"
      ],
      "cwd": "${workspaceFolder}/frontend",
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}

6. ํ…Œ์ŠคํŠธ ๋ชจ๋ฒ” ์‚ฌ๋ก€

ํ…Œ์ŠคํŠธ ๊ตฌ์กฐ (AAA ํŒจํ„ด)

it("์„ค๋ช…์ ์ธ ํ…Œ์ŠคํŠธ ์ด๋ฆ„", async () => {
  // Arrange (์ค€๋น„)
  const input = { year: "2024", term: "1ํ•™๊ธฐ" };

  // Act (์‹คํ–‰)
  const result = await createSeason(input);

  // Assert (๊ฒ€์ฆ)
  expect(result.year).toBe("2024");
  expect(result.isActivated).toBe(false);
});

describe ๋ธ”๋ก์œผ๋กœ ๊ทธ๋ฃนํ™”

describe("SeasonController", () => {
  describe("create", () => {
    it("์œ ํšจํ•œ ๋ฐ์ดํ„ฐ๋กœ ํ•™๊ธฐ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค", () => { ... });
    it("ํ•„์ˆ˜ ํ•„๋“œ๊ฐ€ ์—†์œผ๋ฉด 400์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค", () => { ... });
    it("์ค‘๋ณต ํ•™๊ธฐ ์ƒ์„ฑ ์‹œ 409๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค", () => { ... });
  });

  describe("find", () => {
    it("ํ•™๊ธฐ ๋ชฉ๋ก์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค", () => { ... });
    it("ํŠน์ • ํ•™๊ธฐ๋ฅผ ID๋กœ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค", () => { ... });
  });

  describe("remove", () => {
    it("ํ•™๊ธฐ๋ฅผ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค", () => { ... });
    it("์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ•™๊ธฐ ์‚ญ์ œ ์‹œ 404๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค", () => { ... });
  });
});

ํ…Œ์ŠคํŠธ ๊ฒฉ๋ฆฌ

๊ฐ ํ…Œ์ŠคํŠธ๋Š” ๋…๋ฆฝ์ ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ๊ฐ„ ์ƒํƒœ ๊ณต์œ ๋ฅผ ํ”ผํ•˜์‹ญ์‹œ์˜ค:

describe("UserService", () => {
  // ๊ฐ ํ…Œ์ŠคํŠธ ์ „์— ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”
  beforeEach(async () => {
    await clearTestData();
  });

  // ๋ชจ๋“  ํ…Œ์ŠคํŠธ ํ›„ ์—ฐ๊ฒฐ ์ •๋ฆฌ
  afterAll(async () => {
    await closeConnections();
  });

  it("ํ…Œ์ŠคํŠธ 1", () => { ... });
  it("ํ…Œ์ŠคํŠธ 2", () => { ... }); // ํ…Œ์ŠคํŠธ 1์˜ ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š์Œ
});

์˜๋ฏธ ์žˆ๋Š” ํ…Œ์ŠคํŠธ ์ด๋ฆ„

ํ…Œ์ŠคํŠธ ์ด๋ฆ„์€ ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑํ•˜๋ฉฐ, ๋ฌด์—‡์„ ํ…Œ์ŠคํŠธํ•˜๋Š”์ง€ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค:

// ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ์‹œ - ๊ตฌ์ฒด์ ์ด๊ณ  ๋ช…ํ™•ํ•œ ์ด๋ฆ„
it("๊ด€๋ฆฌ์ž๊ฐ€ ํ•™๊ธฐ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ๋น„ํ™œ์„ฑํ™” ์ƒํƒœ๋กœ ์ €์žฅ๋˜์–ด์•ผ ํ•œ๋‹ค", () => { ... });
it("ํ•„์ˆ˜ ํ•„๋“œ(school)๊ฐ€ ๋ˆ„๋ฝ๋˜๋ฉด SCHOOL_REQUIRED ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค", () => { ... });
it("์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋…„๋„/ํ•™๊ธฐ ์กฐํ•ฉ์œผ๋กœ ์ƒ์„ฑ ์‹œ 409๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค", () => { ... });

// ์ž˜๋ชป๋œ ์˜ˆ์‹œ - ๋ชจํ˜ธํ•œ ์ด๋ฆ„
it("ํ…Œ์ŠคํŠธ 1", () => { ... });
it("์ •์ƒ ๋™์ž‘", () => { ... });
it("์—๋Ÿฌ ์ฒ˜๋ฆฌ", () => { ... });

ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ํ™•์ธ

# ํ”„๋ก ํŠธ์—”๋“œ ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ
cd frontend
yarn test -- --coverage --watchAll=false

# ๋ฐฑ์—”๋“œ ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ
cd backend
yarn test -- --coverage

์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ๋Š” coverage/ ๋””๋ ‰ํ† ๋ฆฌ์— ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. coverage/lcov-report/index.html์„ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์—ด์–ด ์‹œ๊ฐ์ ์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๋ชฉ์ฐจ๋กœ ๋Œ์•„๊ฐ€๊ธฐ