TDD

1️⃣ TDDλž€?

Test Driven Develpment의 μ•½μžλ‘œμ¨ ν…ŒμŠ€νŠΈ 주도 κ°œλ°œμ΄λΌλŠ” λœ»μ„ κ°€μ§„λ‹€. λ‹€μ‹œλ§ν•΄, 짧은 개발 사이클을 λ°˜λ³΅ν•˜λŠ” ν”„λ‘œμ„ΈμŠ€λ‘œ, ν…ŒμŠ€νŠΈλ‘œ ν•˜μ—¬κΈˆ 전체적인 κ°œλ°œμ„ μ£Όλ„ν•˜κ²Œλ”ν•˜λŠ” 방법둠 쀑 ν•˜λ‚˜μ΄λ‹€. 기쑴에 μ‚¬μš©ν•˜λ˜ κ°œλ°œλ°©λ²•μ€ (섀계 ➑️ 개발 ➑️ ν…ŒμŠ€νŠΈ) ➑️ μˆ˜μ • ➑️ 반볡의 μˆœμ„œλ₯Ό λ”°λžλ‹€λ©΄, TDDλŠ” (섀계 ➑️ μˆ˜μ • ➑️ ν…ŒμŠ€νŠΈ) ➑️ 반볡 ➑️ 개발의 μˆœμ„œλ₯Ό κ°€μ§„λ‹€.

2️⃣ κΈ°μ‘΄ κ°œλ°œλ°©μ‹μ˜ 단점

기쑴에 μ‚¬μš©ν•˜λ˜ κ°œλ°œλ°©μ‹μ€ μ‚¬μš©μžμ˜ μš”κ΅¬μ‚¬ν•­μ΄ μ²˜μŒλΆ€ν„° λͺ…ν™•ν•˜μ§€ μ•Šμ„μˆ˜ 있기 λ•Œλ¬Έμ— κ°œλ°œμ΄ν›„ μ½”λ“œλ₯Ό μž¬μ„€κ³„ν•΄μ•Ό ν•˜λŠ” κ²½μš°κ°€ κ½€ λ°œμƒν•œλ‹€. μ—¬κΈ°μ„œ κΈ°μ‘΄ κ°œλ°œλ°©μ‹μ˜ 단점이 λ°œμƒν•œλ‹€.

  • κ°œλ°œμžκ°€ μ½”λ“œλ₯Ό μž¬μ„€κ³„(μ½”λ“œλ₯Ό μˆ˜μ •, μ‚­μ œ, μ‚½μž…)ν•˜λŠ” κ³Όμ •μ—μ„œ λΆˆν•„μš”ν•œ μ½”λ“œκ°€ λ‚¨κ²¨μ§€κ±°λ‚˜ μ€‘λ³΅λ˜λŠ” μ½”λ“œκ°€ 생길 κ°€λŠ₯성이 λ†’λ‹€.

  • μž‘μ€ κΈ°λŠ₯ μˆ˜μ •μ—λ„ 전체적인 뢀뢄을 ν…ŒμŠ€νŠΈν•΄μ•Ό ν•˜λ―€λ‘œ 버그 κ²€μΆœμ΄ νž˜λ“€μ–΄μ§€κ³ , μ–΄λ””μ„œ 버그가 λ°œμƒν• μ§€ λͺ¨λ₯΄λ―€λ‘œ 전체적인 μ†ŒμŠ€μ½”λ“œμ˜ ν’ˆμ§ˆμ΄ μ €ν•˜λœλ‹€.

μ΅œμ’…μ μœΌλ‘œ μž¬μ‚¬μš©μ΄ μ–΄λ ΅κ³ , μœ μ§€λ³΄μˆ˜κ°€ νž˜λ“€μ–΄μ§€κ³  ν…ŒμŠ€νŠΈ λΉ„μš©μ΄ λ†’μ•„μ§€λŠ” κ²°κ³Όλ₯Ό λ‚³λŠ”λ‹€.

3️⃣ TDD λ°©μ‹μ˜ μž₯단점

TDD의 경우, 기쑴의 κ°œλ°œλ°©μ‹κ³Ό 큰 차이점이 ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„± ν›„ μ‹€μ œ μ½”λ“œλ₯Ό μž‘μ„± ν•œλ‹€λŠ” 점이닀.

μž₯점

  • ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€λ₯Ό μž‘μ„±ν•˜λ©° μƒκΈ°λŠ” μˆ˜μ •μ‚¬ν•­κ³Ό 버그가 λ°œμƒν•œ 뢀뢄을 μ¦‰μ‹œ μΆ”κ°€ν•˜λ―€λ‘œ 버그 κ²€μΆœλŠ₯λ ₯ μƒμŠΉ 및 μ†ŒμŠ€μ½”λ“œμ˜ κ°„κ²°ν™”

  • λ‹€λ₯Έ κ°œλ°œμžμ™€μ˜ ν˜‘μ—…μ‹œ ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€κ°€ μ‘΄μž¬ν•˜κΈ° λ•Œλ¬Έμ— λΉ λ₯Έ μ½”λ“œμ˜ 이해와 쀑점을 λ‘μ—ˆλ˜ λΆ€λΆ„ 곡유 κ°€λŠ₯

단점

  • ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€κ°€ μ¦κ°€ν• μˆ˜λ‘ μ‹€μ œ 개발 μ†ŒμŠ€μ½”λ“œλ₯Ό μ™„μ„±ν•˜κΈ°κΉŒμ§€μ˜ μ‹œκ°„μ΄ μ¦κ°€ν•œλ‹€.

  • κΈ°μ‘΄ κ°œλ°œλ°©μ‹μ΄ 이미 λͺΈμ— μ²΄ν™”λœ 경우, TDD 방식을 μ μš©ν•˜κΈ° νž˜λ“€λ‹€.

4️⃣ TDD μ˜ˆμ‹œ

GitHub μ†ŒμŠ€μ½”λ“œ

JavaScriptμ—μ„œ μ‚¬μš©λ˜λŠ” ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬μΈ Jestλ₯Ό μ‚¬μš©ν•˜μ—¬ κ°„λ‹¨ν•œ μ‚¬μš©μ˜ˆμ‹œλ₯Ό λ“€μ–΄λ³΄μž.

λ¨Όμ €pnpm init 와 pnpm install --save-dev jestλ₯Ό μž…λ ₯ν•˜μ—¬ package.json νŒŒμΌμ„ λ§Œλ“€κ³  Jestλ₯Ό 개발 μ˜μ‘΄μ„±μœΌλ‘œ μ„€μΉ˜ν•œλ‹€.

κ°„λ‹¨νžˆ ν…ŒμŠ€νŠΈν•  파일 ꡬ쑰이닀.

β”œβ”€β”€ node_modules
β”‚   └── jest -> .pnpm/jest@29.7.0/node_modules/jest
β”œβ”€β”€ package.json
β”œβ”€β”€ pnpm-lock.yaml
β”œβ”€β”€ sum.js
└── sum.test.js
package.json

{
  "name": "tdd_test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "jest": "^29.7.0"
  }
}

package.json의 test μ»€λ§¨λ“œλ₯Ό jest둜 μˆ˜μ •ν•˜μ—¬ pnpm testλ₯Ό μ‹€ν–‰ν•˜μ˜€μ„λ•Œ jest둜 ν•˜μ—¬κΈˆ ν…ŒμŠ€νŠΈ ν• μˆ˜ μžˆλ„λ‘ λ§Œλ“€μ–΄ μ€€λ‹€.

맀우 κ°„λ‹¨ν•œ λ”ν•˜κΈ° κΈ°λŠ₯을 λ§Œλ“€κ±΄λ°, μ˜ˆμ™Έλ₯Ό 두기 μœ„ν•΄ λ§€κ°œλ³€μˆ˜μ— μˆ«μžκ°€ μ•„λ‹Œ 값이 λ“€μ–΄μ˜€λ©΄ "μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€"λ₯Ό λ¦¬ν„΄ν•΄μ•Όν•œλ‹€λŠ” 가정을 μΆ”κ°€ν•˜μž.

κ·Έλ ‡λ‹€λ©΄ ν…ŒμŠ€νŠΈν•΄μ•Όν•  사항듀은

  1. μ–‘μˆ˜/음수 + μ–‘μˆ˜/음수

  2. λ¬Έμžμ—΄ + μ–‘μˆ˜ / 음수

정도가 μžˆμ„ 것 κ°™λ‹€.

μœ„ 2κ°€μ§€ μ‘°κ±΄μ—μ„œ Fail을 κ²€μΆœν•΄μ•Ό ν•˜λŠ” 쑰건은 2λ²ˆμ΄λ‹€. λ¨Όμ € μœ„ λ‘κ°€μ§€μ˜ 쑰건을 μΆ©μ‘±ν•˜λŠ” ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€λ₯Ό μž‘μ„±ν•΄λ³΄μž

const sum = require("./sum");

describe("ν…ŒμŠ€νŠΈ κ·Έλ£Ή", () => {
  test("1κ³Ό 2λ₯Ό λ”ν•˜λ©΄ 3이닀", () => {
    expect(sum(1, 2)).toBe(3);
  });
  test("1κ³Ό -3을 λ”ν•˜λ©΄ -2이닀", () => {
    expect(sum(1, -3)).toBe(-2);
  });
  test("-3κ³Ό -10을 λ”ν•˜λ©΄ -13이닀.", () => {
    expect(sum(-3, -10)).toBe(-13);
  });
  test("ν•˜λ‚˜μ™€ λ‘˜μ€ μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.", () => {
    expect(sum("ν•˜λ‚˜", "λ‘˜")).toBe("μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.");
  });
});

총 4κ°€μ§€μ˜ ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€λ₯Ό μž‘μ„±ν•΄λ³΄μ•˜λŠ”λ°, 각각 μ–‘μˆ˜+μ–‘μˆ˜, μ–‘μˆ˜+음수, 음수+μ–‘μˆ˜, 그리고 sumμ΄λΌλŠ” ν•¨μˆ˜μ— ν•„μš”ν•œ λ§€κ°œλ³€μˆ˜ 쀑 ν•˜λ‚˜κ°€ λ¬Έμžμ—΄μΈ 경우 2κ°œμ™€ 두 λ§€κ°œλ³€μˆ˜κ°€ λ¬Έμžμ—΄μΈ 경우λ₯Ό μž‘μ„±ν–ˆλ‹€.

이제 ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€μ˜ 신뒰성을 μœ„ν•΄ λ¬Έμžμ—΄μ΄ λ§€κ°œλ³€μˆ˜λ‘œ λ“€μ–΄κ°„ ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€κ°€ Fail을 λ¦¬ν„΄ν•˜λŠ” 검증 μ½”λ“œλ₯Ό μž‘μ„±ν•œν›„ ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•΄λ³΄μž

sum.js

function sum(a, b) {
  return a + b;
}

module.exports = sum;

pnpm test μ»€λ§¨λ“œλ₯Ό μž…λ ₯ν•˜μ—¬ μ‹€ν–‰ν•˜κ²Œ 되면

pnpm test      

> tdd_test@1.0.0 test /Users/chungyeonkim/Desktop/tdd_test
> jest

 FAIL  ./sum.test.js
  ν…ŒμŠ€νŠΈ κ·Έλ£Ή
    βœ“ 1κ³Ό 2λ₯Ό λ”ν•˜λ©΄ 3이닀 (2 ms)
    βœ“ 1κ³Ό -3을 λ”ν•˜λ©΄ -2이닀
    βœ“ -3κ³Ό -10을 λ”ν•˜λ©΄ -13이닀.
    βœ• ν•˜λ‚˜λŠ” μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€. (2 ms)
    βœ• λ‘˜μ€ μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.
    βœ• ν•˜λ‚˜μ™€ λ‘˜μ€ μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.

  ● ν…ŒμŠ€νŠΈ κ·Έλ£Ή β€Ί ν•˜λ‚˜λŠ” μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.

    expect(received).toBe(expected) // Object.is equality

    Expected: "μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€."
    Received: "ν•˜λ‚˜2"

      12 |   });
      13 |   test("ν•˜λ‚˜λŠ” μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.", () => {
    > 14 |     expect(sum("ν•˜λ‚˜", 2)).toBe("μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.");
         |                          ^
      15 |   });
      16 |   test("λ‘˜μ€ μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.", () => {
      17 |     expect(sum(1, "λ‘˜")).toBe("μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.");

      at Object.toBe (sum.test.js:14:26)

  ● ν…ŒμŠ€νŠΈ κ·Έλ£Ή β€Ί λ‘˜μ€ μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.

    expect(received).toBe(expected) // Object.is equality

    Expected: "μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€."
    Received: "1λ‘˜"

      15 |   });
      16 |   test("λ‘˜μ€ μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.", () => {
    > 17 |     expect(sum(1, "λ‘˜")).toBe("μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.");
         |                         ^
      18 |   });
      19 |   test("ν•˜λ‚˜μ™€ λ‘˜μ€ μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.", () => {
      20 |     expect(sum("ν•˜λ‚˜", "λ‘˜")).toBe("μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.");

      at Object.toBe (sum.test.js:17:25)

  ● ν…ŒμŠ€νŠΈ κ·Έλ£Ή β€Ί ν•˜λ‚˜μ™€ λ‘˜μ€ μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.

    expect(received).toBe(expected) // Object.is equality

    Expected: "μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€."
    Received: "ν•˜λ‚˜λ‘˜"

      18 |   });
      19 |   test("ν•˜λ‚˜μ™€ λ‘˜μ€ μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.", () => {
    > 20 |     expect(sum("ν•˜λ‚˜", "λ‘˜")).toBe("μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.");
         |                            ^
      21 |   });
      22 | });
      23 |

      at Object.toBe (sum.test.js:20:28)

Test Suites: 1 failed, 1 total
Tests:       3 failed, 3 passed, 6 total
Snapshots:   0 total
Time:        0.267 s, estimated 1 s
Ran all test suites.
 ELIFECYCLE  Test failed. See above for more details.

μœ„μ²˜λŸΌ ν…ŒμŠ€νŠΈ κ²°κ³Όκ°€ λ‚˜μ˜€κ²Œ λœλ‹€.

μ˜λ„ν•œλŒ€λ‘œ λ¬Έμžμ—΄μ΄ λ§€κ°œλ³€μˆ˜μ— λ“€μ–΄κ°„ κ²½μš°λŠ” λͺ¨λ‘ Fail을 λ¦¬ν„΄ν•˜μ˜€μœΌλ―€λ‘œ, 이제 ν•΄λ‹Ή μΌ€μ΄μŠ€λ“€μ΄ λͺ¨λ‘ Successλ₯Ό ν•  수 μžˆλ„λ‘ μ½”λ“œλ₯Ό μˆ˜μ •ν•œλ‹€.

sum.js

function sum(a, b) {
  if (typeof a !== "number" || typeof b !== "number") {
    return "μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.";
  }
  return a + b;
}

module.exports = sum;

이제 λ‹€μ‹œ ν•œλ²ˆ ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•΄λ³΄μž

sum.test.js

pnpm test

> tdd_test@1.0.0 test /Users/chungyeonkim/Desktop/tdd_test
> jest

 PASS  ./sum.test.js
  ν…ŒμŠ€νŠΈ κ·Έλ£Ή
    βœ“ 1κ³Ό 2λ₯Ό λ”ν•˜λ©΄ 3이닀 (1 ms)
    βœ“ 1κ³Ό -3을 λ”ν•˜λ©΄ -2이닀
    βœ“ -3κ³Ό -10을 λ”ν•˜λ©΄ -13이닀.
    βœ“ ν•˜λ‚˜λŠ” μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€. (1 ms)
    βœ“ λ‘˜μ€ μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.
    βœ“ ν•˜λ‚˜μ™€ λ‘˜μ€ μˆ«μžκ°€ μ•„λ‹™λ‹ˆλ‹€.

Test Suites: 1 passed, 1 total
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        0.147 s, estimated 1 s
Ran all test suites.

ν˜„μž¬ μž‘μ„±ν•œ ν…ŒμŠ€νŠΈκ°€ λͺ¨λ‘ ν†΅κ³Όν–ˆκΈ° λ•Œλ¬Έμ— ν•΄λ‹Ή μ½”λ“œλ₯Ό κ°œλ°œλ‹¨κ³„λ‘œ κ°€μ Έκ°€ μ‚¬μš©ν• μˆ˜ μžˆλ‹€λŠ” 뜻이 λœλ‹€.

5️⃣ 정리

κ°„λ‹¨νžˆ μš”μ•½ν•˜μžλ©΄ TDD λŠ” κ°œλ°œν•˜λ €λŠ” κΈ°λŠ₯의 ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€λ₯Ό 미리 μž‘μ„±μ„ ν•˜κ³  이에 λ§žμΆ”μ–΄ κ°œλ°œν•˜λŠ” 방식 쀑 ν•˜λ‚˜μ΄λ‹€.

μ•žμ„œ TDDλ°©μ‹μ˜ λ‹¨μ μœΌλ‘œ 꼽힌 ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€κ°€ μ¦κ°€ν• μˆ˜λ‘ μ‹€μ œ 개발 μ†ŒμŠ€μ½”λ“œλ₯Ό μ™„μ„±ν•˜κΈ°κΉŒμ§€μ˜ μ‹œκ°„μ΄ μ¦κ°€ν•œλ‹€λŠ” 점은 ν”„λ‘œκ·Έλž¨μ˜ 전체 개발/μœ μ§€λ³΄μˆ˜μ— λ“€μ–΄κ°€λŠ” λ¦¬μ†ŒμŠ€λ₯Ό κ³ λ €ν•œλ‹€λ©΄ 였히렀 TDDλ°©μ‹μ˜ μž₯점을 잘 λ‚˜νƒ€λ‚΄λŠ” 뢀뢄이라고 μƒκ°ν•œλ‹€.

기쑴의 κ°œλ°œλ°©μ‹μœΌλ‘œ μ†ŒμŠ€μ½”λ“œλ₯Ό μž‘μ„±μ™„λ£Œ ν›„ μ˜ˆμ™Έλ₯Ό μƒκ°ν•˜λ©° ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€λ₯Ό μž‘μ„±ν•˜κ³ , 버그가 λ°œμƒν•œ 뢀뢄을 λ””λ²„κΉ…ν•˜λŠ” 것보닀 TDD방식을 μ‚¬μš©ν•˜λŠ”κ²ƒμ΄ 훨씬 νŽΈλ¦¬ν•˜κ³  νš¨μœ¨μ μ΄λΌλŠ”κ²ƒμ€ λΆ€μ •ν• μˆ˜ μ—†λŠ” 사싀이닀. TDD방식을 μ‚¬μš©ν•˜μ—¬ κ°œλ°œμ„ ν–ˆλ‹€λŠ” 것 μžμ²΄κ°€ κ²°κ΅­ κ°œλ°œμžκ°€ μƒκ°ν• μˆ˜ μžˆλŠ” μ˜ˆμ™Έμ— λŒ€ν•΄μ„œ λͺ¨λ‘ μ²˜λ¦¬κ°€ λ˜μ—ˆλ‹€λŠ” λœ»μ΄λΌλŠ” 것이기 λ•Œλ¬Έμ΄λ‹€.

이 과정을 거쳐 전체 개발 μ†ŒμŠ€μ½”λ“œλ₯Ό μž‘μ„±μ™„λ£Œν•˜μ˜€λ‹€λ©΄ μ†ŒμŠ€μ½”λ“œκ°€ κ°„κ²°ν•˜κ³  각각의 κΈ°λŠ₯에 λŒ€ν•œ λͺ¨λ“  ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€κ°€ μ‘΄μž¬ν•˜κΈ°μ— λ‹€λ₯Έ κ°œλ°œμžκ°€ μΈμˆ˜μΈκ³„ 받더라도 ν•΄λ‹Ή μ½”λ“œλ₯Ό μœ μ§€λ³΄μˆ˜ν•˜κΈ° μˆ˜μ›”ν•΄μ§„λ‹€.

즉, TDD 방식은 ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€μ— νˆ¬μžν•œ λ¦¬μ†ŒμŠ€κ°€ κ²°κ΅­ μž₯기적으둜 μœ μ§€λ³΄μˆ˜μ— 맀우 μš©μ΄ν•΄μ Έ 전체 개발/μœ μ§€λ³΄μˆ˜μ— λ“€μ–΄κ°€λŠ” λ¦¬μ†ŒμŠ€λ₯Ό 상당뢀뢄 λ‹¨μΆ•μ‹œμΌœμ£ΌλŠ” 방식이기 λ•Œλ¬Έμ— 기쑴의 κ°œλ°œλ°©μ‹λ³΄λ‹€ 훨씬 효율적이라고 μƒκ°λœλ‹€.

Last updated