TDD 실습 (1)

노력하는백엔드·2026년 3월 8일

이론 정리

목록 보기
11/14

실습 주제

  • 간단한 메모 CRUD

메모 등록

Red

  • 메모 등록에 값의 범위, 필수 값 누락 등에 대한 테스트 진행

    • title : 누락, 공백, 타입 오류
    • period : 누락, 내부 필든 누락, 날짜 순서 날짜 형식 오류
    • content : 누락, 공백, 타입 오류
    • priority : 누락, 타입 오류, 범위 오류
  • 코드

    const { createMemo } = require("../../src/memo/createMemo");
    
    describe("createMemo", () => {
      const validInput = {
        title: "TDD 메모 작성",
        period: {
          startDate: "2026-03-06",
          endDate: "2026-03-10",
        },
        content: "Red 단계 테스트 작성",
        priority: 3,
      };
    
      test("정상 입력이면 memo 생성에 성공한다", async () => {
        const result = await createMemo(validInput);
    
        expect(result).toMatchObject({
          title: "TDD 메모 작성",
          period: {
            startDate: "2026-03-06",
            endDate: "2026-03-10",
          },
          content: "Red 단계 테스트 작성",
          priority: 3,
          isSuccess: false,
        });
      });
    
      test("title이 없으면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            title: "",
          }),
        ).rejects.toThrow("title");
      });
    
      test("title이 공백만 있으면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            title: "   ",
          }),
        ).rejects.toThrow("title");
      });
    
      test("title이 문자열이 아니면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            title: 1234,
          }),
        ).rejects.toThrow("title");
      });
    
      test("period가 없으면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            period: null,
          }),
        ).rejects.toThrow("period");
      });
    
      test("period의 startDate가 없으면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            period: {
              endDate: "2026-03-10",
            },
          }),
        ).rejects.toThrow("period");
      });
    
      test("period의 endDate가 없으면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            period: {
              startDate: "2026-03-06",
            },
          }),
        ).rejects.toThrow("period");
      });
    
      test("기간 시작일이 종료일보다 뒤면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            period: {
              startDate: "2026-03-11",
              endDate: "2026-03-10",
            },
          }),
        ).rejects.toThrow("period");
      });
    
      test("period 날짜 형식이 올바르지 않으면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            period: {
              startDate: "wrong-date",
              endDate: "2026-03-10",
            },
          }),
        ).rejects.toThrow("period");
      });
    
      test("content가 없으면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            content: "",
          }),
        ).rejects.toThrow("content");
      });
    
      test("content가 공백만 있으면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            content: "   ",
          }),
        ).rejects.toThrow("content");
      });
    
      test("content가 문자열이 아니면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            content: 1234,
          }),
        ).rejects.toThrow("content");
      });
    
      test("priority가 없으면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            priority: undefined,
          }),
        ).rejects.toThrow("priority");
      });
    
      test("priority가 숫자가 아니면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            priority: "3",
          }),
        ).rejects.toThrow("priority");
      });
    
      test("priority가 1보다 작으면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            priority: 0,
          }),
        ).rejects.toThrow("priority");
      });
    
      test("priority가 5보다 크면 에러 발생", async () => {
        await expect(
          createMemo({
            ...validInput,
            priority: 6,
          }),
        ).rejects.toThrow("priority");
      });
    });
  • 실행 로그

    hongseongchae@hongseongchaeui-MacBookPro tdd_study % npm test
    
    > tdd_study@1.0.0 test
    > jest
    
     FAIL  __test__/memo/createMemo.test.js
      ● createMemo › 정상 입력이면 memo 생성에 성공한다
    
    TypeError: createMemo is not a function
    
      13 |
      14 |   test("정상 입력이면 memo 생성에 성공한다", async () => {
    > 15 |     const result = await createMemo(validInput);
         |                          ^
      16 |
      17 |     expect(result).toMatchObject({
      18 |       title: "TDD 메모 작성",
    
      at Object.createMemo (__test__/memo/createMemo.test.js:15:26)
    
     ● createMemo › title이 없으면 에러 발생
    
    TypeError: createMemo is not a function
    
      29 |   test("title이 없으면 에러 발생", async () => {
      30 |     await expect(
    > 31 |       createMemo({
         |       ^
      32 |         ...validInput,
      33 |         title: "",
      34 |       }),
    
      at Object.createMemo (__test__/memo/createMemo.test.js:31:7)
    
     ● createMemo › title이 공백만 있으면 에러 발생
    
    TypeError: createMemo is not a function
    
      38 |   test("title이 공백만 있으면 에러 발생", async () => {
      39 |     await expect(
    > 40 |       createMemo({
         |       ^
      41 |         ...validInput,
      42 |         title: "   ",
      43 |       }),
    
      at Object.createMemo (__test__/memo/createMemo.test.js:40:7)
    
    ● createMemo › title이 문자열이 아니면 에러 발생
    
    TypeError: createMemo is not a function
    
      47 |   test("title이 문자열이 아니면 에러 발생", async () => {
      48 |     await expect(
    > 49 |       createMemo({
         |       ^
      50 |         ...validInput,
      51 |         title: 1234,
      52 |       }),
    
      at Object.createMemo (__test__/memo/createMemo.test.js:49:7)
    
    ● createMemo › period가 없으면 에러 발생
    
    TypeError: createMemo is not a function
    
      56 |   test("period가 없으면 에러 발생", async () => {
      57 |     await expect(
    > 58 |       createMemo({
         |       ^
      59 |         ...validInput,
      60 |         period: null,
      61 |       }),
    
      at Object.createMemo (__test__/memo/createMemo.test.js:58:7)
    
    ● createMemo › period의 startDate가 없으면 에러 발생
    
    TypeError: createMemo is not a function
    
      65 |   test("period의 startDate가 없으면 에러 발생", async () => {
      66 |     await expect(
    > 67 |       createMemo({
         |       ^
      68 |         ...validInput,
      69 |         period: {
      70 |           endDate: "2026-03-10",
    
      at Object.createMemo (__test__/memo/createMemo.test.js:67:7)
    
    ● createMemo › period의 endDate가 없으면 에러 발생
    
    TypeError: createMemo is not a function
    
      76 |   test("period의 endDate가 없으면 에러 발생", async () => {
      77 |     await expect(
    > 78 |       createMemo({
         |       ^
      79 |         ...validInput,
      80 |         period: {
      81 |           startDate: "2026-03-06",
    
      at Object.createMemo (__test__/memo/createMemo.test.js:78:7)
    
    ● createMemo › 기간 시작일이 종료일보다 뒤면 에러 발생
    
    TypeError: createMemo is not a function
    
      87 |   test("기간 시작일이 종료일보다 뒤면 에러 발생", async () => {
      88 |     await expect(
    > 89 |       createMemo({
         |       ^
      90 |         ...validInput,
      91 |         period: {
      92 |           startDate: "2026-03-11",
    
      at Object.createMemo (__test__/memo/createMemo.test.js:89:7)
    
    ● createMemo › period 날짜 형식이 올바르지 않으면 에러 발생
    
    TypeError: createMemo is not a function
    
       99 |   test("period 날짜 형식이 올바르지 않으면 에러 발생", async () => {
      100 |     await expect(
    > 101 |       createMemo({
          |       ^
      102 |         ...validInput,
      103 |         period: {
      104 |           startDate: "wrong-date",
    
      at Object.createMemo (__test__/memo/createMemo.test.js:101:7)
    
    ● createMemo › content가 없으면 에러 발생
    
    TypeError: createMemo is not a function
    
      111 |   test("content가 없으면 에러 발생", async () => {
      112 |     await expect(
    > 113 |       createMemo({
          |       ^
      114 |         ...validInput,
      115 |         content: "",
      116 |       }),
    
      at Object.createMemo (__test__/memo/createMemo.test.js:113:7)
    
    ● createMemo › content가 공백만 있으면 에러 발생
    
    TypeError: createMemo is not a function
    
      120 |   test("content가 공백만 있으면 에러 발생", async () => {
      121 |     await expect(
    > 122 |       createMemo({
          |       ^
      123 |         ...validInput,
      124 |         content: "   ",
      125 |       }),
    
      at Object.createMemo (__test__/memo/createMemo.test.js:122:7)
    
    ● createMemo › content가 문자열이 아니면 에러 발생
    
    TypeError: createMemo is not a function
    
      129 |   test("content가 문자열이 아니면 에러 발생", async () => {
      130 |     await expect(
    > 131 |       createMemo({
          |       ^
      132 |         ...validInput,
      133 |         content: 1234,
      134 |       }),
    
      at Object.createMemo (__test__/memo/createMemo.test.js:131:7)
    
    ● createMemo › priority가 없으면 에러 발생
    
    TypeError: createMemo is not a function
    
      138 |   test("priority가 없으면 에러 발생", async () => {
      139 |     await expect(
    > 140 |       createMemo({
          |       ^
      141 |         ...validInput,
      142 |         priority: undefined,
      143 |       }),
    
      at Object.createMemo (__test__/memo/createMemo.test.js:140:7)
    
    ● createMemo › priority가 숫자가 아니면 에러 발생
    
    TypeError: createMemo is not a function
    
      147 |   test("priority가 숫자가 아니면 에러 발생", async () => {
      148 |     await expect(
    > 149 |       createMemo({
          |       ^
      150 |         ...validInput,
      151 |         priority: "3",
      152 |       }),
    
      at Object.createMemo (__test__/memo/createMemo.test.js:149:7)
    
    ● createMemo › priority가 1보다 작으면 에러 발생
    
    TypeError: createMemo is not a function
    
      156 |   test("priority가 1보다 작으면 에러 발생", async () => {
      157 |     await expect(
    > 158 |       createMemo({
          |       ^
      159 |         ...validInput,
      160 |         priority: 0,
      161 |       }),
    
      at Object.createMemo (__test__/memo/createMemo.test.js:158:7)
    
    ● createMemo › priority가 5보다 크면 에러 발생
    
      TypeError: createMemo is not a function
    
      165 |   test("priority가 5보다 크면 에러 발생", async () => {
      166 |     await expect(
    > 167 |       createMemo({
          |       ^
      168 |         ...validInput,
      169 |         priority: 6,
      170 |       }),
    
      at Object.createMemo (__test__/memo/createMemo.test.js:167:7)
            Test Suites: 1 failed, 1 total
            Tests:       16 failed, 16 total
            Snapshots:   0 total
            Time:        1.01 s

Red인 경우 테스트 코드를 정의하는 단계(실패하는 케이스)이므로 코드가 구현되어있지않으므로 모두 실패로 나온다.

Green

  • Red를 바탕으로 실행 코드를 작성한다

  • 코드

      async function createMemo(input) {
      const { title, period, content, priority } = input;
    
      if (typeof title !== "string" || title.trim() === "") {
        throw new Error("title");
      }
    
      if (
        !period ||
        typeof period.startDate !== "string" ||
        typeof period.endDate !== "string"
      ) {
        throw new Error("period");
      }
    
      const start = new Date(period.startDate);
      const end = new Date(period.endDate);
    
      if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
        throw new Error("period");
      }
    
      if (start > end) {
        throw new Error("period");
      }
    
      if (typeof content !== "string" || content.trim() === "") {
        throw new Error("content");
      }
    
      if (typeof priority !== "number" || priority < 1 || priority > 5) {
        throw new Error("priority");
      }
    
      return {
        title,
        period,
        content,
        priority,
        isSuccess: false,
      };
    }
    
    module.exports = { createMemo };
    
  • 실행 로그

    hongseongchae@hongseongchaeui-MacBookPro tdd_study % npm test
    
    > tdd_study@1.0.0 test
    > jest
    
     PASS  __test__/memo/createMemo.test.js
      createMemo
        ✓ 정상 입력이면 memo 생성에 성공한다 (3 ms)
        ✓ title이 없으면 에러 발생 (8 ms)
        ✓ title이 공백만 있으면 에러 발생 (3 ms)
        ✓ title이 문자열이 아니면 에러 발생 (1 ms)
        ✓ period가 없으면 에러 발생
        ✓ period의 startDate가 없으면 에러 발생 (1 ms)
        ✓ period의 endDate가 없으면 에러 발생 (1 ms)
        ✓ 기간 시작일이 종료일보다 뒤면 에러 발생 (1 ms)
        ✓ period 날짜 형식이 올바르지 않으면 에러 발생 (1 ms)
        ✓ content가 없으면 에러 발생
        ✓ content가 공백만 있으면 에러 발생 (1 ms)
        ✓ content가 문자열이 아니면 에러 발생
        ✓ priority가 없으면 에러 발생
        ✓ priority가 숫자가 아니면 에러 발생 (1 ms)
        ✓ priority가 1보다 작으면 에러 발생 (1 ms)
        ✓ priority가 5보다 크면 에러 발생
    
      Test Suites: 1 passed, 1 total
      Tests:       16 passed, 16 total
      Snapshots:   0 total
      Time:        0.215 s, estimated 1 s
      Ran all test suites.
    

blue

  • Green을 통과한 기준으로 refactoring를 진행한다.

  • 검증함수 분리

  • 코드

      function validateTitle(title) {
        if (typeof title !== "string" || title.trim() === "") {
          throw new Error("title");
        }
      }
    
      function validateContent(content) {
        if (typeof content !== "string" || content.trim() === "") {
          throw new Error("content");
        }
      }
    
      function validatePriority(priority) {
        if (typeof priority !== "number" || priority < 1 || priority > 5) {
          throw new Error("priority");
        }
      }
    
      function validatePeriod(period) {
        if (
          !period ||
          typeof period.startDate !== "string" ||
          typeof period.endDate !== "string"
        ) {
          throw new Error("period");
        }
    
        if (!isValidDateRange(period.startDate, period.endDate)) {
          throw new Error("period");
        }
      }
    
      function isValidDateRange(startDate, endDate) {
        const start = new Date(startDate);
        const end = new Date(endDate);
    
        if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
          return false;
        }
    
        if (start > end) {
          return false;
        }
    
        return true;
      }
    
      async function createMemo(input) {
        const { title, period, content, priority } = input;
    
        validateTitle(title);
        validatePeriod(period);
        validateContent(content);
        validatePriority(priority);
    
        return {
          title,
          period,
          content,
          priority,
          isSuccess: false,
        };
      }
    
      module.exports = { createMemo };
    
  • 실행 로그

      hongseongchae@hongseongchaeui-MacBookPro tdd_study % npm test
    
      > tdd_study@1.0.0 test
      > jest
    
       PASS  __test__/memo/createMemo.test.js
        createMemo
          ✓ 정상 입력이면 memo 생성에 성공한다 (3 ms)
          ✓ title이 없으면 에러 발생 (8 ms)
          ✓ title이 공백만 있으면 에러 발생 (1 ms)
          ✓ title이 문자열이 아니면 에러 발생 (1 ms)
          ✓ period가 없으면 에러 발생 (1 ms)
          ✓ period의 startDate가 없으면 에러 발생 (1 ms)
          ✓ period의 endDate가 없으면 에러 발생 (1 ms)
          ✓ 기간 시작일이 종료일보다 뒤면 에러 발생 (1 ms)
          ✓ period 날짜 형식이 올바르지 않으면 에러 발생 (1 ms)
          ✓ content가 없으면 에러 발생 (1 ms)
          ✓ content가 공백만 있으면 에러 발생 (2 ms)
          ✓ content가 문자열이 아니면 에러 발생 (1 ms)
          ✓ priority가 없으면 에러 발생
          ✓ priority가 숫자가 아니면 에러 발생 (1 ms)
          ✓ priority가 1보다 작으면 에러 발생
          ✓ priority가 5보다 크면 에러 발생
    
      Test Suites: 1 passed, 1 total
      Tests:       16 passed, 16 total
      Snapshots:   0 total
      Time:        0.339 s, estimated 1 s
      Ran all test suites.
    
profile
열심히 노력하는 백엔드입니다.

0개의 댓글