TDD 실습 (4)

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

이론 정리

목록 보기
14/14
  • 메모 업데이트
    • Red
      • 메모 업데이트에 대한 필수 값 누락, 검증에 대한 테스트 진행
        • id 필요
        • id 타입 / 범위 검증
        • 수정할 데이터 필요
          • title
          • period
          • content
          • priority
          • isSuccess
        • 존재하지 않는 id 에러
        • 수정 성공 시 수정된 객체 반환
      • 코드
        const { updateMemo } = require("../../src/memo/updateMemo");
        
        describe("updateMemo", () => {
          test("정상 id와 수정값이면 memo 수정에 성공한다", async () => {
            const result = await updateMemo(1, {
              title: "수정된 메모 제목",
              content: "수정된 메모 내용",
              priority: 5,
              isSuccess: true,
            });
        
            expect(result).toMatchObject({
              id: 1,
              title: "수정된 메모 제목",
              content: "수정된 메모 내용",
              priority: 5,
              isSuccess: true,
            });
          });
        
          test("id가 없으면 에러 발생", async () => {
            await expect(
              updateMemo(undefined, {
                title: "수정 제목",
              }),
            ).rejects.toThrow("id");
          });
        
          test("id가 문자열이면 에러 발생", async () => {
            await expect(
              updateMemo("1", {
                title: "수정 제목",
              }),
            ).rejects.toThrow("id");
          });
        
          test("id가 0이면 에러 발생", async () => {
            await expect(
              updateMemo(0, {
                title: "수정 제목",
              }),
            ).rejects.toThrow("id");
          });
        
          test("update data가 없으면 에러 발생", async () => {
            await expect(updateMemo(1)).rejects.toThrow("data");
          });
        
          test("존재하지 않는 id이면 에러 발생", async () => {
            await expect(
              updateMemo(999, {
                title: "수정 제목",
              }),
            ).rejects.toThrow("memo");
          });
        
          test("title이 빈 문자열이면 에러 발생", async () => {
            await expect(
              updateMemo(1, {
                title: "",
              }),
            ).rejects.toThrow("title");
          });
        
          test("content가 공백만 있으면 에러 발생", async () => {
            await expect(
              updateMemo(1, {
                content: "   ",
              }),
            ).rejects.toThrow("content");
          });
        
          test("priority가 범위를 벗어나면 에러 발생", async () => {
            await expect(
              updateMemo(1, {
                priority: 6,
              }),
            ).rejects.toThrow("priority");
          });
        
          test("isSuccess가 boolean이 아니면 에러 발생", async () => {
            await expect(
              updateMemo(1, {
                isSuccess: "true",
              }),
            ).rejects.toThrow("isSuccess");
          });
        
          test("period 시작일이 종료일보다 뒤면 에러 발생", async () => {
            await expect(
              updateMemo(1, {
                period: {
                  startDate: "2026-03-20",
                  endDate: "2026-03-10",
                },
              }),
            ).rejects.toThrow("period");
          });
        });
        
      • 실행 결과
        hongseongchae@hongseongchaeui-MacBookPro tdd_study % npm test
        
        > tdd_study@1.0.0 test
        > jest
        
         FAIL  __test__/memo/updateMemoList.test.js
          ● updateMemo › 정상 id와 수정값이면 memo 수정에 성공한다
        
            TypeError: updateMemo is not a function
        
              3 | describe("updateMemo", () => {
              4 |   test("정상 id와 수정값이면 memo 수정에 성공한다", async () => {
            > 5 |     const result = await updateMemo(1, {
                |                          ^
              6 |       title: "수정된 메모 제목",
              7 |       content: "수정된 메모 내용",
              8 |       priority: 5,
        
              at Object.updateMemo (__test__/memo/updateMemoList.test.js:5:26)
        
          ● updateMemo › id가 없으면 에러 발생
        
            TypeError: updateMemo is not a function
        
              21 |   test("id가 없으면 에러 발생", async () => {
              22 |     await expect(
            > 23 |       updateMemo(undefined, {
                 |       ^
              24 |         title: "수정 제목",
              25 |       }),
              26 |     ).rejects.toThrow("id");
        
              at Object.updateMemo (__test__/memo/updateMemoList.test.js:23:7)
        
          ● updateMemo › id가 문자열이면 에러 발생
        
            TypeError: updateMemo is not a function
        
              29 |   test("id가 문자열이면 에러 발생", async () => {
              30 |     await expect(
            > 31 |       updateMemo("1", {
                 |       ^
              32 |         title: "수정 제목",
              33 |       }),
              34 |     ).rejects.toThrow("id");
        
              at Object.updateMemo (__test__/memo/updateMemoList.test.js:31:7)
        
          ● updateMemo › id0이면 에러 발생
        
            TypeError: updateMemo is not a function
        
              37 |   test("id가 0이면 에러 발생", async () => {
              38 |     await expect(
            > 39 |       updateMemo(0, {
                 |       ^
              40 |         title: "수정 제목",
              41 |       }),
              42 |     ).rejects.toThrow("id");
        
              at Object.updateMemo (__test__/memo/updateMemoList.test.js:39:7)
        
          ● updateMemo › update data가 없으면 에러 발생
        
            TypeError: updateMemo is not a function
        
              44 |
              45 |   test("update data가 없으면 에러 발생", async () => {
            > 46 |     await expect(updateMemo(1)).rejects.toThrow("data");
                 |                  ^
              47 |   });
              48 |
              49 |   test("존재하지 않는 id이면 에러 발생", async () => {
        
              at Object.updateMemo (__test__/memo/updateMemoList.test.js:46:18)
        
          ● updateMemo › 존재하지 않는 id이면 에러 발생
        
            TypeError: updateMemo is not a function
        
              49 |   test("존재하지 않는 id이면 에러 발생", async () => {
              50 |     await expect(
            > 51 |       updateMemo(999, {
                 |       ^
              52 |         title: "수정 제목",
              53 |       }),
              54 |     ).rejects.toThrow("memo");
        
              at Object.updateMemo (__test__/memo/updateMemoList.test.js:51:7)
        
          ● updateMemo › title이 빈 문자열이면 에러 발생
        
            TypeError: updateMemo is not a function
        
              57 |   test("title이 빈 문자열이면 에러 발생", async () => {
              58 |     await expect(
            > 59 |       updateMemo(1, {
                 |       ^
              60 |         title: "",
              61 |       }),
              62 |     ).rejects.toThrow("title");
        
              at Object.updateMemo (__test__/memo/updateMemoList.test.js:59:7)
        
          ● updateMemo › content가 공백만 있으면 에러 발생
        
            TypeError: updateMemo is not a function
        
              65 |   test("content가 공백만 있으면 에러 발생", async () => {
              66 |     await expect(
            > 67 |       updateMemo(1, {
                 |       ^
              68 |         content: "   ",
              69 |       }),
              70 |     ).rejects.toThrow("content");
        
              at Object.updateMemo (__test__/memo/updateMemoList.test.js:67:7)
        
          ● updateMemo › priority가 범위를 벗어나면 에러 발생
        
            TypeError: updateMemo is not a function
        
              73 |   test("priority가 범위를 벗어나면 에러 발생", async () => {
              74 |     await expect(
            > 75 |       updateMemo(1, {
                 |       ^
              76 |         priority: 6,
              77 |       }),
              78 |     ).rejects.toThrow("priority");
        
              at Object.updateMemo (__test__/memo/updateMemoList.test.js:75:7)
        
          ● updateMemo › isSuccess가 boolean이 아니면 에러 발생
        
            TypeError: updateMemo is not a function
        
              81 |   test("isSuccess가 boolean이 아니면 에러 발생", async () => {
              82 |     await expect(
            > 83 |       updateMemo(1, {
                 |       ^
              84 |         isSuccess: "true",
              85 |       }),
              86 |     ).rejects.toThrow("isSuccess");
        
              at Object.updateMemo (__test__/memo/updateMemoList.test.js:83:7)
        
          ● updateMemo › period 시작일이 종료일보다 뒤면 에러 발생
        
            TypeError: updateMemo is not a function
        
              89 |   test("period 시작일이 종료일보다 뒤면 에러 발생", async () => {
              90 |     await expect(
            > 91 |       updateMemo(1, {
                 |       ^
              92 |         period: {
              93 |           startDate: "2026-03-20",
              94 |           endDate: "2026-03-10",
        
              at Object.updateMemo (__test__/memo/updateMemoList.test.js:91:7)
        
         PASS  __test__/memo/deleteMemo.test.js
         PASS  __test__/memo/createMemo.test.js
         PASS  __test__/memo/readMemo.test.js
         PASS  __test__/memo/readMemoList.test.js
        
        Test Suites: 1 failed, 4 passed, 5 total
        Tests:       11 failed, 43 passed, 54 total
        Snapshots:   0 total
        Time:        0.696 s, estimated 1 s
        Ran all test suites.
      • Red인 경우 테스트 코드를 정의하는 단계(실패하는 케이스)이므로 실질적인 구현이 없으므로 모두 실패가 나온다
    • Green
      • Red를 바탕으로 테스트 코드를 구현한다
      • 코드
        const mockMemos = [
          {
            id: 1,
            title: "TDD 메모 작성",
            period: {
              startDate: "2026-03-06",
              endDate: "2026-03-10",
            },
            content: "첫 번째 메모",
            priority: 3,
            isSuccess: false,
          },
          {
            id: 2,
            title: "두 번째 메모",
            period: {
              startDate: "2026-03-07",
              endDate: "2026-03-12",
            },
            content: "두 번째 내용",
            priority: 2,
            isSuccess: true,
          },
          {
            id: 3,
            title: "세 번째 메모",
            period: {
              startDate: "2026-03-08",
              endDate: "2026-03-15",
            },
            content: "세 번째 내용",
            priority: 3,
            isSuccess: true,
          },
          {
            id: 4,
            title: "네 번째 메모",
            period: {
              startDate: "2026-03-09",
              endDate: "2026-03-18",
            },
            content: "네 번째 내용",
            priority: 1,
            isSuccess: false,
          },
        ];
        
        async function updateMemo(id, data) {
          if (typeof id !== "number" || id <= 0) {
            throw new Error("id");
          }
        
          if (!data) {
            throw new Error("data");
          }
        
          const index = mockMemos.findIndex((item) => item.id === id);
        
          if (index === -1) {
            throw new Error("memo");
          }
        
          if (data.title !== undefined) {
            if (typeof data.title !== "string" || data.title.trim() === "") {
              throw new Error("title");
            }
          }
        
          if (data.content !== undefined) {
            if (typeof data.content !== "string" || data.content.trim() === "") {
              throw new Error("content");
            }
          }
        
          if (data.priority !== undefined) {
            if (
              typeof data.priority !== "number" ||
              data.priority < 1 ||
              data.priority > 5
            ) {
              throw new Error("priority");
            }
          }
        
          if (data.isSuccess !== undefined) {
            if (typeof data.isSuccess !== "boolean") {
              throw new Error("isSuccess");
            }
          }
        
          if (data.period !== undefined) {
            if (
              !data.period ||
              typeof data.period.startDate !== "string" ||
              typeof data.period.endDate !== "string"
            ) {
              throw new Error("period");
            }
        
            const start = new Date(data.period.startDate);
            const end = new Date(data.period.endDate);
        
            if (
              Number.isNaN(start.getTime()) ||
              Number.isNaN(end.getTime()) ||
              start > end
            ) {
              throw new Error("period");
            }
          }
        
          mockMemos[index] = {
            ...mockMemos[index],
            ...data,
          };
        
          return mockMemos[index];
        }
        
        module.exports = { updateMemo };
        
      • 실행결과
        hongseongchae@hongseongchaeui-MacBookPro tdd_study % npm test
        
        > tdd_study@1.0.0 test
        > jest
        
         PASS  __test__/memo/updateMemoList.test.js
         PASS  __test__/memo/createMemo.test.js
         PASS  __test__/memo/readMemo.test.js
         PASS  __test__/memo/readMemoList.test.js
         PASS  __test__/memo/deleteMemo.test.js
        
        Test Suites: 5 passed, 5 total
        Tests:       54 passed, 54 total
        Snapshots:   0 total
        Time:        0.599 s, estimated 1 s
        Ran all test suites.
    • blue
      • Green을 통과한 기준으로 refactoring을 한다
      • validation 함수를 분리
      • 코드
        const mockMemos = [
          {
            id: 1,
            title: "TDD 메모 작성",
            period: {
              startDate: "2026-03-06",
              endDate: "2026-03-10",
            },
            content: "첫 번째 메모",
            priority: 3,
            isSuccess: false,
          },
          {
            id: 2,
            title: "두 번째 메모",
            period: {
              startDate: "2026-03-07",
              endDate: "2026-03-12",
            },
            content: "두 번째 내용",
            priority: 2,
            isSuccess: true,
          },
          {
            id: 3,
            title: "세 번째 메모",
            period: {
              startDate: "2026-03-08",
              endDate: "2026-03-15",
            },
            content: "세 번째 내용",
            priority: 3,
            isSuccess: true,
          },
          {
            id: 4,
            title: "네 번째 메모",
            period: {
              startDate: "2026-03-09",
              endDate: "2026-03-18",
            },
            content: "네 번째 내용",
            priority: 1,
            isSuccess: false,
          },
        ];
        
        function validateId(id) {
          if (typeof id !== "number" || id <= 0) {
            throw new Error("id");
          }
        }
        
        function validateUpdateData(data) {
          if (!data) {
            throw new Error("data");
          }
        }
        
        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 validateIsSuccess(isSuccess) {
          if (typeof isSuccess !== "boolean") {
            throw new Error("isSuccess");
          }
        }
        
        function validatePeriod(period) {
          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()) ||
            start > end
          ) {
            throw new Error("period");
          }
        }
        
        function validateUpdateFields(data) {
          if (data.title !== undefined) {
            validateTitle(data.title);
          }
        
          if (data.content !== undefined) {
            validateContent(data.content);
          }
        
          if (data.priority !== undefined) {
            validatePriority(data.priority);
          }
        
          if (data.isSuccess !== undefined) {
            validateIsSuccess(data.isSuccess);
          }
        
          if (data.period !== undefined) {
            validatePeriod(data.period);
          }
        }
        
        async function updateMemo(id, data) {
          validateId(id);
          validateUpdateData(data);
        
          const index = mockMemos.findIndex((item) => item.id === id);
        
          if (index === -1) {
            throw new Error("memo");
          }
        
          validateUpdateFields(data);
        
          mockMemos[index] = {
            ...mockMemos[index],
            ...data,
          };
        
          return mockMemos[index];
        }
        
        module.exports = { updateMemo };
        
      • 실행 결과
        hongseongchae@hongseongchaeui-MacBookPro tdd_study % npm test
        
        > tdd_study@1.0.0 test
        > jest
        
         PASS  __test__/memo/updateMemoList.test.js
         PASS  __test__/memo/createMemo.test.js
         PASS  __test__/memo/readMemoList.test.js
         PASS  __test__/memo/readMemo.test.js
         PASS  __test__/memo/deleteMemo.test.js
        
        Test Suites: 5 passed, 5 total
        Tests:       54 passed, 54 total
        Snapshots:   0 total
        Time:        0.586 s, estimated 1 s
        Ran all test suites.
profile
열심히 노력하는 백엔드입니다.

0개의 댓글