ํฌ์ŠคํŠธ

๐Ÿ”ด๐ŸŸขโ™ป๏ธ TDD ์ž…๋ฌธ ๊ฐ€์ด๋“œ โ€” 10๋…„์งธ ๋ฒฝ ์•ž์— ์„  ๋‹น์‹ ์„ ์œ„ํ•ด

๐Ÿ”ด๐ŸŸขโ™ป๏ธ TDD ์ž…๋ฌธ ๊ฐ€์ด๋“œ โ€” 10๋…„์งธ ๋ฒฝ ์•ž์— ์„  ๋‹น์‹ ์„ ์œ„ํ•ด

โ€œ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ์—†์œผ๋ฉด ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ๋ฅผ ํ•œ ์ค„๋„ ์“ฐ์ง€ ์•Š๋Š”๋‹ค.โ€
โ€” Robert C. Martin (Uncle Bob)


๐Ÿ“‹ ๋ชฉ์ฐจ

  1. TDD๋ž€ ๋ฌด์—‡์ธ๊ฐ€ โ€” 5๋ถ„ ์š”์•ฝ
  2. Uncle Bob์˜ 3๊ฐ€์ง€ ๋ฒ•์น™
  3. Red โ†’ Green โ†’ Refactor ์‚ฌ์ดํด
  4. ์ฒ˜์Œ ํ•ด๋ณด๋Š” TDD ์‹ค์ „ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
  5. TDD ์—ฐ์Šต ์‚ฌ์ดํŠธ & ๋„๊ตฌ
  6. ์ž…๋ฌธ์ž๊ฐ€ ๊ฐ€์žฅ ๋งŽ์ด ํ•˜๋Š” ์‹ค์ˆ˜
  7. ์˜ค๋Š˜ ๋‹น์žฅ ์‹œ์ž‘ํ•˜๋Š” ๋ฐฉ๋ฒ•

1. TDD๋ž€ ๋ฌด์—‡์ธ๊ฐ€ โ€” 5๋ถ„ ์š”์•ฝ

๋Œ€๋ถ€๋ถ„์˜ ๊ฐœ๋ฐœ์ž๋Š” ์ด๋ ‡๊ฒŒ ์ฝ”๋”ฉํ•ฉ๋‹ˆ๋‹ค:

1
์ฝ”๋“œ ์ž‘์„ฑ โ†’ ์‹คํ–‰ โ†’ ๋ฒ„๊ทธ ๋ฐœ๊ฒฌ โ†’ ์ˆ˜์ • โ†’ ๋ฐ˜๋ณต

TDD๋Š” ์ˆœ์„œ๋ฅผ ๋’ค์ง‘์Šต๋‹ˆ๋‹ค:

1
ํ…Œ์ŠคํŠธ ๋จผ์ € ์ž‘์„ฑ โ†’ ์‹คํŒจ ํ™•์ธ โ†’ ์ฝ”๋“œ ์ž‘์„ฑ โ†’ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ โ†’ ์ฝ”๋“œ ์ •๋ฆฌ

์™œ ์ˆœ์„œ๋ฅผ ๋’ค์ง‘์„๊นŒ์š”?

ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ € ์“ฐ๋ฉด โ€œ๋‚ด๊ฐ€ ๋งŒ๋“ค ๊ธฐ๋Šฅ์ด ์ •ํ™•ํžˆ ๋ฌด์—‡์ธ์ง€โ€๋ฅผ ์ฝ”๋“œ๋ฅผ ์งœ๊ธฐ ์ „์— ๋ช…ํ™•ํžˆ ์ •์˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ฒญ์‚ฌ์ง„ ์—†์ด ์ง‘์„ ์ง“์ง€ ์•Š๋“ฏ, ๊ธฐ๋Šฅ ๋ช…์„ธ(ํ…Œ์ŠคํŠธ) ์—†์ด ์ฝ”๋“œ๋ฅผ ์งœ์ง€ ์•Š๋Š” ๊ฒƒ์ด TDD์˜ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.

๐Ÿ’ก ๋น„์œ : ์‹œํ—˜ ๋ฌธ์ œ์ง€(ํ…Œ์ŠคํŠธ)๋ฅผ ๋จผ์ € ๋ฐ›๊ณ  ๋‹ต์•ˆ์ง€(์ฝ”๋“œ)๋ฅผ ์“ฐ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
์‹œํ—˜ ๋ฒ”์œ„๋ฅผ ๋ฏธ๋ฆฌ ์•Œ๋ฉด ๋ถˆํ•„์š”ํ•œ ๊ณต๋ถ€๋ฅผ ํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค.


2. Uncle Bob์˜ 3๊ฐ€์ง€ ๋ฒ•์น™

TDD์˜ ๊ทœ์น™์€ ๋”ฑ ์„ธ ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. ๋‹จ์ˆœํ•˜์ง€๋งŒ ๊ฐ•๋ ฅํ•ฉ๋‹ˆ๋‹ค.

๋ฒ•์น™๋‚ด์šฉ
๋ฒ•์น™ 1์‹คํŒจํ•˜๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๊ฐ€ ์—†์œผ๋ฉด ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ๋ฅผ ํ•œ ์ค„๋„ ์ž‘์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค
๋ฒ•์น™ 2์ปดํŒŒ์ผ์— ์‹คํŒจํ•  ์ •๋„ ์ด์ƒ์˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ์ž‘์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค
๋ฒ•์น™ 3ํ˜„์žฌ ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผ์‹œํ‚ฌ ๋งŒํผ๋งŒ ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค

์™œ ์ด ๊ทœ์น™๋“ค์ด ์ค‘์š”ํ•œ๊ฐ€?

  • ๋ฒ•์น™ 1: ํ•ญ์ƒ ๋ชฉํ‘œ(ํ…Œ์ŠคํŠธ)๋ฅผ ๋จผ์ € ์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ฐฉํ–ฅ ์—†์ด ๋‹ฌ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๋ฒ•์น™ 2: ํ…Œ์ŠคํŠธ๋ฅผ ํ•œ ๋ฒˆ์— ๋„ˆ๋ฌด ๋งŽ์ด ์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ž‘์€ ๊ฑธ์Œ์œผ๋กœ ๋‚˜์•„๊ฐ‘๋‹ˆ๋‹ค.
  • ๋ฒ•์น™ 3: ์ง€๊ธˆ ๋‹น์žฅ ํ•„์š” ์—†๋Š” ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค (YAGNI ์›์น™).

์„ธ ๋ฒ•์น™์„ ๋”ฐ๋ฅด๋ฉด ํ…Œ์ŠคํŠธ์™€ ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ๊ฐ€ ๊ฑฐ์˜ ์ค„ ๋‹จ์œ„๋กœ ๋ฒˆ๊ฐˆ์•„ ์ž‘์„ฑ๋ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ ์ฝ”๋“œ ์ „์ฒด๊ฐ€ ํ•ญ์ƒ ํ…Œ์ŠคํŠธ๋กœ ๋ณดํ˜ธ๋ฐ›๋Š” ์ƒํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.


3. Red โ†’ Green โ†’ Refactor ์‚ฌ์ดํด

TDD๋Š” ์ด ์„ธ ๋‹จ๊ณ„๋ฅผ ๋ฐ˜๋ณตํ•˜๋Š” ๋ฆฌ๋“ฌ์ž…๋‹ˆ๋‹ค. ๋ณดํ†ต ํ•œ ์‚ฌ์ดํด์€ 2~5๋ถ„ ์•ˆ์— ๋๋‚ฉ๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
        โ”‚                                         โ”‚
        โ–ผ                                         โ”‚
   ๐Ÿ”ด RED                                         โ”‚
   ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ ์ž‘์„ฑ                            โ”‚
   (์•„์ง ์ฝ”๋“œ๊ฐ€ ์—†์œผ๋‹ˆ ๋‹น์—ฐํžˆ ์‹คํŒจ)               โ”‚
        โ”‚                                         โ”‚
        โ–ผ                                         โ”‚
   ๐ŸŸข GREEN                                       โ”‚
   ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผ์‹œํ‚ค๋Š” ์ตœ์†Œํ•œ์˜ ์ฝ”๋“œ ์ž‘์„ฑ          โ”‚
   (์šฐ์•„ํ•˜์ง€ ์•Š์•„๋„ ๊ดœ์ฐฎ๋‹ค, ์ผ๋‹จ ํ†ต๊ณผ!)           โ”‚
        โ”‚                                         โ”‚
        โ–ผ                                         โ”‚
   โ™ป๏ธ REFACTOR                                    โ”‚
   ํ…Œ์ŠคํŠธ๋Š” ์œ ์ง€ํ•œ ์ฑ„, ์ฝ”๋“œ ๊ตฌ์กฐ๋ฅผ ์ •๋ฆฌ           โ”‚
   (์ค‘๋ณต ์ œ๊ฑฐ, ๋„ค์ด๋ฐ ๊ฐœ์„ , ๊ตฌ์กฐ ๊ฐœ์„ )            โ”‚
        โ”‚                                         โ”‚
        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     ๋ฐ˜๋ณต

ํ•ต์‹ฌ ๋งˆ์Œ๊ฐ€์ง

  • ๐Ÿ”ด Red: โ€œ์ด ๊ธฐ๋Šฅ์€ ์•„์ง ์—†๋‹คโ€๋Š” ๊ฒƒ์„ ์ฆ๋ช…ํ•ฉ๋‹ˆ๋‹ค
  • ๐ŸŸข Green: โ€œ์ผ๋‹จ ๋™์ž‘ํ•˜๊ฒŒโ€ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ฝ”๋“œ๊ฐ€ ๋”๋Ÿฝ๊ณ  ํ•˜๋“œ์ฝ”๋”ฉ์ด์–ด๋„ OK
  • โ™ป๏ธ Refactor: โ€œ๊นจ๋—ํ•˜๊ฒŒโ€ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๊ฐ€ ์•ˆ์ „๋ง ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค

4. ์ฒ˜์Œ ํ•ด๋ณด๋Š” TDD ์‹ค์ „ ์‹œ๋ฎฌ๋ ˆ์ด์…˜

FizzBuzz ๋ฌธ์ œ๋กœ TDD๋ฅผ ๊ฒฝํ—˜ํ•ด ๋ด…์‹œ๋‹ค.

๋ฌธ์ œ ์ •์˜: 1๋ถ€ํ„ฐ N๊นŒ์ง€ ์ˆซ์ž๋ฅผ ์ถœ๋ ฅํ•˜๋˜,

  • 3์˜ ๋ฐฐ์ˆ˜์ด๋ฉด "Fizz"
  • 5์˜ ๋ฐฐ์ˆ˜์ด๋ฉด "Buzz"
  • 15์˜ ๋ฐฐ์ˆ˜์ด๋ฉด "FizzBuzz"
  • ๋‚˜๋จธ์ง€๋Š” ์ˆซ์ž ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅ

4.1 SpringBoot + JUnit 5 ์˜ˆ์ œ

ํ™˜๊ฒฝ ์ค€๋น„

  • pom.xml์— ์•„๋ž˜ ์˜์กด์„ฑ ์ถ”๊ฐ€
1
2
3
4
5
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

๐Ÿ”ด STEP 1: ์ฒซ ๋ฒˆ์งธ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ (RED)

์•„์ง FizzBuzz ํด๋ž˜์Šค๋Š” ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ์กฐ์ฐจ ์•ˆ ๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์ •์ƒ์ž…๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/test/java/com/example/FizzBuzzTest.java

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

class FizzBuzzTest {

    @Test
    void ์ˆซ์ž_1์€_1์„_๋ฐ˜ํ™˜ํ•œ๋‹ค() {
        FizzBuzz fizzBuzz = new FizzBuzz();
        assertThat(fizzBuzz.convert(1)).isEqualTo("1");
    }
}

ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด? โ†’ ์ปดํŒŒ์ผ ์—๋Ÿฌ (FizzBuzz ํด๋ž˜์Šค๊ฐ€ ์—†์œผ๋‹ˆ๊นŒ)
์ด๊ฒƒ์ด RED ์ƒํƒœ์ž…๋‹ˆ๋‹ค. โœ… ์ •์ƒ์ž…๋‹ˆ๋‹ค.


๐ŸŸข STEP 2: ์ตœ์†Œํ•œ์˜ ์ฝ”๋“œ๋กœ ํ†ต๊ณผ (GREEN)

ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผ์‹œํ‚ฌ ๊ฐ€์žฅ ๋‹จ์ˆœํ•œ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
ํ•˜๋“œ์ฝ”๋”ฉ์ด์–ด๋„ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ์ผ๋‹จ ์ดˆ๋ก๋ถˆ์ด ๋ชฉํ‘œ์ž…๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
// src/main/java/com/example/FizzBuzz.java

public class FizzBuzz {

    public String convert(int number) {
        return "1"; // ๐Ÿ˜‚ ํ•˜๋“œ์ฝ”๋”ฉ! ํ•˜์ง€๋งŒ ์ง€๊ธˆ์€ ์ด๊ฒƒ์œผ๋กœ ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค
    }
}

ํ…Œ์ŠคํŠธ ์‹คํ–‰ โ†’ GREEN ๐ŸŸข


๐Ÿ”ด STEP 3: ๋‘ ๋ฒˆ์งธ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€ (RED)

ํ•˜๋“œ์ฝ”๋”ฉ์„ ๊นจ๋œจ๋ฆฌ๋Š” ์ƒˆ ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

1
2
3
4
void ์ˆซ์ž_2๋Š”_2๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค() {
    FizzBuzz fizzBuzz = new FizzBuzz();
    assertThat(fizzBuzz.convert(2)).isEqualTo("2");
}

์‹คํ–‰ โ†’ RED ๐Ÿ”ด ("1" ํ•˜๋“œ์ฝ”๋”ฉ์ด 2๋ฅผ ์ฒ˜๋ฆฌ ๋ชป ํ•จ)


๐ŸŸข STEP 4: ๋‘ ํ…Œ์ŠคํŠธ ๋ชจ๋‘ ํ†ต๊ณผ (GREEN)

1
2
3
public String convert(int number) {
    return String.valueOf(number); // ์ด์ œ ์ˆซ์ž๋ฅผ ๋ฌธ์ž๋กœ ๋ณ€ํ™˜
}

์‹คํ–‰ โ†’ GREEN ๐ŸŸข (๋‘ ํ…Œ์ŠคํŠธ ๋ชจ๋‘ ํ†ต๊ณผ)


๐Ÿ”ด STEP 5: 3์˜ ๋ฐฐ์ˆ˜ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€ (RED)

1
2
3
4
void ์ˆซ์ž_3์€_Fizz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค() {
    FizzBuzz fizzBuzz = new FizzBuzz();
    assertThat(fizzBuzz.convert(3)).isEqualTo("Fizz");
}

์‹คํ–‰ โ†’ RED ๐Ÿ”ด


๐ŸŸข STEP 6: 3์˜ ๋ฐฐ์ˆ˜ ์ฒ˜๋ฆฌ (GREEN)

1
2
3
4
public String convert(int number) {
    if (number % 3 == 0) return "Fizz";
    return String.valueOf(number);
}

์‹คํ–‰ โ†’ GREEN ๐ŸŸข


๐Ÿ”ด STEP 7: 5์˜ ๋ฐฐ์ˆ˜ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€ (RED)

1
2
3
4
void ์ˆซ์ž_5๋Š”_Buzz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค() {
    FizzBuzz fizzBuzz = new FizzBuzz();
    assertThat(fizzBuzz.convert(5)).isEqualTo("Buzz");
}

์‹คํ–‰ โ†’ RED ๐Ÿ”ด


๐ŸŸข STEP 8: 5์˜ ๋ฐฐ์ˆ˜ ์ฒ˜๋ฆฌ (GREEN)

1
2
3
4
5
6
public String convert(int number) {
    if (number % 15 == 0) return "FizzBuzz"; // 15์˜ ๋ฐฐ์ˆ˜ ๋จผ์ € ์ฒดํฌ!
    if (number % 3 == 0) return "Fizz";
    if (number % 5 == 0) return "Buzz";
    return String.valueOf(number);
}

๐Ÿ”ด STEP 9: 15์˜ ๋ฐฐ์ˆ˜ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€ ํ›„ GREEN ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void ์ˆซ์ž_15๋Š”_FizzBuzz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค() {
    FizzBuzz fizzBuzz = new FizzBuzz();
    assertThat(fizzBuzz.convert(15)).isEqualTo("FizzBuzz");
}

void ์ˆซ์ž_9๋Š”_Fizz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค() {
    FizzBuzz fizzBuzz = new FizzBuzz();
    assertThat(fizzBuzz.convert(9)).isEqualTo("Fizz");
}

void ์ˆซ์ž_10์€_Buzz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค() {
    FizzBuzz fizzBuzz = new FizzBuzz();
    assertThat(fizzBuzz.convert(10)).isEqualTo("Buzz");
}

๋ชจ๋‘ GREEN ๐ŸŸข


โ™ป๏ธ STEP 10: ๋ฆฌํŒฉํ„ฐ๋ง (REFACTOR)

์ค‘๋ณต๋œ new FizzBuzz()๋ฅผ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๋Š” ๋ฐ”๋€Œ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// src/test/java/com/example/FizzBuzzTest.java

class FizzBuzzTest {

    private final FizzBuzz fizzBuzz = new FizzBuzz(); // ์ค‘๋ณต ์ œ๊ฑฐ

    @Test
    void ์ˆซ์ž_1์€_1์„_๋ฐ˜ํ™˜ํ•œ๋‹ค() {
        assertThat(fizzBuzz.convert(1)).isEqualTo("1");
    }

    @Test
    void ์ˆซ์ž_3์€_Fizz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค() {
        assertThat(fizzBuzz.convert(3)).isEqualTo("Fizz");
    }

    @Test
    void ์ˆซ์ž_5๋Š”_Buzz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค() {
        assertThat(fizzBuzz.convert(5)).isEqualTo("Buzz");
    }

    @Test
    void ์ˆซ์ž_15๋Š”_FizzBuzz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค() {
        assertThat(fizzBuzz.convert(15)).isEqualTo("FizzBuzz");
    }

    @Test
    void ์ˆซ์ž_9๋Š”_Fizz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค() {
        assertThat(fizzBuzz.convert(9)).isEqualTo("Fizz");
    }

    @Test
    void ์ˆซ์ž_10์€_Buzz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค() {
        assertThat(fizzBuzz.convert(10)).isEqualTo("Buzz");
    }
}
1
2
3
4
5
6
7
8
9
10
11
// src/main/java/com/example/FizzBuzz.java (์ตœ์ข…)

public class FizzBuzz {

    public String convert(int number) {
        if (number % 15 == 0) return "FizzBuzz";
        if (number % 3 == 0) return "Fizz";
        if (number % 5 == 0) return "Buzz";
        return String.valueOf(number);
    }
}

๋ฆฌํŒฉํ„ฐ๋ง ํ›„ ์ „์ฒด ํ…Œ์ŠคํŠธ ์‹คํ–‰ โ†’ ๋ชจ๋‘ GREEN ๐ŸŸข โœ…


4.2 Go + testing ํŒจํ‚ค์ง€ ์˜ˆ์ œ

Go๋Š” ๋ณ„๋„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—†์ด ํ‘œ์ค€ testing ํŒจํ‚ค์ง€๋งŒ์œผ๋กœ TDD๋ฅผ ์‹ค์ฒœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ™˜๊ฒฝ ์ค€๋น„

1
2
mkdir fizzbuzz && cd fizzbuzz
go mod init fizzbuzz

๐Ÿ”ด STEP 1: ํ…Œ์ŠคํŠธ ํŒŒ์ผ ๋จผ์ € ์ƒ์„ฑ (RED)

1
2
3
4
5
6
7
8
9
10
11
12
// fizzbuzz_test.go

package fizzbuzz

import "testing"

func TestConvert_์ˆซ์ž_1์€_1์„_๋ฐ˜ํ™˜ํ•œ๋‹ค(t *testing.T) {
    result := Convert(1)
    if result != "1" {
        t.Errorf("Convert(1) = %q, want %q", result, "1")
    }
}
1
2
go test ./...
# FAIL: undefined: Convert โ† ์ •์ƒ์ž…๋‹ˆ๋‹ค. RED ์ƒํƒœ

๐ŸŸข STEP 2: ์ตœ์†Œ ์ฝ”๋“œ ์ž‘์„ฑ (GREEN)

1
2
3
4
5
6
7
// fizzbuzz.go

package fizzbuzz

func Convert(number int) string {
    return "1" // ํ•˜๋“œ์ฝ”๋”ฉ, ์ผ๋‹จ ํ†ต๊ณผ!
}
1
2
go test ./...
# PASS โ† GREEN ๐ŸŸข

๐Ÿ”ด STEP 3 โ†’ GREEN ๋ฐ˜๋ณต

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// fizzbuzz_test.go ์— ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€

func TestConvert_์ˆซ์ž_2๋Š”_2๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค(t *testing.T) {
    result := Convert(2)
    if result != "2" {
        t.Errorf("Convert(2) = %q, want %q", result, "2")
    }
}

func TestConvert_์ˆซ์ž_3์€_Fizz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค(t *testing.T) {
    result := Convert(3)
    if result != "Fizz" {
        t.Errorf("Convert(3) = %q, want %q", result, "Fizz")
    }
}

func TestConvert_์ˆซ์ž_5๋Š”_Buzz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค(t *testing.T) {
    result := Convert(5)
    if result != "Buzz" {
        t.Errorf("Convert(5) = %q, want %q", result, "Buzz")
    }
}

func TestConvert_์ˆซ์ž_15๋Š”_FizzBuzz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค(t *testing.T) {
    result := Convert(15)
    if result != "FizzBuzz" {
        t.Errorf("Convert(15) = %q, want %q", result, "FizzBuzz")
    }
}

๐ŸŸข ์ตœ์ข… ๊ตฌํ˜„ (GREEN)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// fizzbuzz.go (์ตœ์ข…)

package fizzbuzz

import "fmt"

func Convert(number int) string {
    if number%15 == 0 {
        return "FizzBuzz"
    }
    if number%3 == 0 {
        return "Fizz"
    }
    if number%5 == 0 {
        return "Buzz"
    }
    return fmt.Sprintf("%d", number)
}
1
2
3
4
5
6
7
go test ./... -v
# === RUN   TestConvert_์ˆซ์ž_1์€_1์„_๋ฐ˜ํ™˜ํ•œ๋‹ค
# --- PASS: TestConvert_์ˆซ์ž_1์€_1์„_๋ฐ˜ํ™˜ํ•œ๋‹ค (0.00s)
# === RUN   TestConvert_์ˆซ์ž_3์€_Fizz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค
# --- PASS: TestConvert_์ˆซ์ž_3์€_Fizz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค (0.00s)
# ...
# PASS โ† ๋ชจ๋‘ GREEN ๐ŸŸข

โ™ป๏ธ Go ํ…Œ์ด๋ธ” ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ๋กœ ๋ฆฌํŒฉํ„ฐ๋ง (REFACTOR)

Go์—์„œ๋Š” ํ…Œ์ด๋ธ” ๋“œ๋ฆฌ๋ธ ํ…Œ์ŠคํŠธ(Table-Driven Test)๊ฐ€ ๊ด€์šฉ์ ์ธ ๋ฆฌํŒฉํ„ฐ๋ง ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// fizzbuzz_test.go (๋ฆฌํŒฉํ„ฐ๋ง ํ›„)

package fizzbuzz

import "testing"

func TestConvert(t *testing.T) {
    tests := []struct {
        input    int
        expected string
    }{
        {1, "1"},
        {2, "2"},
        {3, "Fizz"},
        {5, "Buzz"},
        {9, "Fizz"},
        {10, "Buzz"},
        {15, "FizzBuzz"},
        {30, "FizzBuzz"},
    }

    for _, tt := range tests {
        t.Run(fmt.Sprintf("Convert(%d)", tt.input), func(t *testing.T) {
            result := Convert(tt.input)
            if result != tt.expected {
                t.Errorf("got %q, want %q", result, tt.expected)
            }
        })
    }
}
1
2
go test ./... -v
# ๋ชจ๋“  ์ผ€์ด์Šค PASS ๐ŸŸข

5. TDD ์—ฐ์Šต ์‚ฌ์ดํŠธ & ๋„๊ตฌ

TDD๋Š” ํ˜ผ์ž ์ฑ…์œผ๋กœ ์ฝ๋Š” ๊ฒƒ๋ณด๋‹ค ์ง์ ‘ ๋ฐ˜๋ณต ์—ฐ์Šต์ด ํ›จ์”ฌ ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.
์•„๋ž˜ ์‚ฌ์ดํŠธ๋“ค์€ ์„ค์น˜ ์—†์ด ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฐ”๋กœ TDD๋ฅผ ์—ฐ์Šตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿฅ‡ Cyber-Dojo (๊ฐ•๋ ฅ ์ถ”์ฒœ โ€” ์ž…๋ฌธ์ž์—๊ฒŒ ์ตœ์ )

์‚ฌ์ดํŠธ: https://cyber-dojo.org

  • ๋ฌด๋ฃŒ, ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฐ”๋กœ ์‚ฌ์šฉ (์„ค์น˜ ๋ถˆํ•„์š”)
  • Java, Go, Python, Ruby ๋“ฑ ์ˆ˜์‹ญ ๊ฐ€์ง€ ์–ธ์–ด ์ง€์›
  • Red ๐Ÿ”ด / Green ๐ŸŸข / Yellow ํŠธ๋ž˜ํ”ฝ ๋ผ์ดํŠธ๋กœ ๋‚ด TDD ๋ฆฌ๋“ฌ์„ ์‹œ๊ฐ์ ์œผ๋กœ ์ถ”์ 
  • FizzBuzz, String Calculator ๋“ฑ ์œ ๋ช… ์นดํƒ€(Kata) ๋‚ด์žฅ
  • ๊ทธ๋ฃน ์—ฐ์Šต๋„ ์ง€์› (์ตœ๋Œ€ 64๋ช… ๋™์‹œ)

์‚ฌ์šฉ ๋ฐฉ๋ฒ•:

  1. https://cyber-dojo.org ์ ‘์†
  2. [I practice] ๋ฒ„ํŠผ ํด๋ฆญ
  3. ์–ธ์–ด(Java ๋˜๋Š” Go) + ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ ์„ ํƒ
  4. ๋ฌธ์ œ ์„ ํƒ (FizzBuzz ๊ถŒ์žฅ) โ†’ [start]
  5. ํ…Œ์ŠคํŠธ ํŒŒ์ผ์„ ๋จผ์ € ์ˆ˜์ • โ†’ [test] ๋ฒ„ํŠผ์œผ๋กœ ์‹คํ–‰
  6. ์ƒ๋‹จ์˜ ํŠธ๋ž˜ํ”ฝ ๋ผ์ดํŠธ ์ƒ‰์ƒ์œผ๋กœ Red/Green ํ™•์ธ

๐Ÿฅˆ Exercism (์–ธ์–ด๋ณ„ ๋‹จ๊ณ„๋ณ„ ํ•™์Šต)

์‚ฌ์ดํŠธ: https://exercism.org

  • ๋ฌด๋ฃŒ, Java์™€ Go ํŠธ๋ž™ ๋ชจ๋‘ ์กด์žฌ
  • ๋ฉ˜ํ† (์‹ค์ œ ๊ฐœ๋ฐœ์ž)๊ฐ€ ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ํ•ด์ค๋‹ˆ๋‹ค
  • ๋ฌธ์ œ๊ฐ€ ๋‚œ์ด๋„ ์ˆœ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์–ด ๋‹จ๊ณ„์ ์œผ๋กœ ์„ฑ์žฅ ๊ฐ€๋Šฅ
  • ๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋„, ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

๐Ÿฅ‰ Coding Dojo Kata (์นดํƒ€ ๋ฌธ์ œ ๋ชจ์Œ)

์‚ฌ์ดํŠธ: https://codingdojo.org/kata

  • TDD์— ์ ํ•ฉํ•œ ์นดํƒ€ ๋ฌธ์ œ ๋ชฉ๋ก ์ œ๊ณต
  • FizzBuzz, String Calculator, Bowling Game, Roman Numerals ๋“ฑ ์ˆ˜์‹ญ ๊ฐ€์ง€
  • ๋ฌธ์ œ๋งŒ ์ œ๊ณตํ•˜๋ฏ€๋กœ ๋‚ด ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ์ง์ ‘ TDD ์ ์šฉ ์—ฐ์Šต์— ์ ํ•ฉ

๊ธฐํƒ€ ์ถ”์ฒœ ์‚ฌ์ดํŠธ

์‚ฌ์ดํŠธURLํŠน์ง•
TDD Buddyhttps://www.tddbuddy.com๊ฐ€์ด๋“œ + ์นดํƒ€ ๋ชจ์Œ, ์ดˆ๋ณด์ž ์นœํ™”์ 
Kata-loghttps://kata-log.rocks/tdd๋‚œ์ด๋„ยทํƒœ๊ทธ๋ณ„ ์นดํƒ€ ๋ถ„๋ฅ˜
Roy Osherove String Calculatorhttps://osherove.com/tdd-kata-1์„ธ๊ณ„์ ์œผ๋กœ ์œ ๋ช…ํ•œ TDD ์ž…๋ฌธ ์นดํƒ€

์ถ”์ฒœ ์นดํƒ€ ์ˆœ์„œ (์ž…๋ฌธ์ž ๋กœ๋“œ๋งต)

1
2
3
4
5
1๋‹จ๊ณ„ (์‹œ์ž‘): FizzBuzz        โ† 30๋ถ„ ๋‚ด ์™„์„ฑ ๊ฐ€๋Šฅ
2๋‹จ๊ณ„ (๊ธฐ์ดˆ): String Calculator โ† ํ•˜๋ฃจ 15๋ถ„์”ฉ 1์ฃผ์ผ
3๋‹จ๊ณ„ (์ค‘๊ธ‰): Roman Numerals   โ† ์•Œ๊ณ ๋ฆฌ์ฆ˜ + TDD ๊ฒฐํ•ฉ
4๋‹จ๊ณ„ (์‹ฌํ™”): Bowling Game      โ† ๊ทœ์น™์ด ๋ณต์žกํ•œ ํ˜„์‹ค์  ๋ฌธ์ œ
5๋‹จ๊ณ„ (ํŒ€):   Tennis Game       โ† ์ƒํƒœ ๊ธฐ๊ณ„ ์„ค๊ณ„ ์—ฐ์Šต

6. ์ž…๋ฌธ์ž๊ฐ€ ๊ฐ€์žฅ ๋งŽ์ด ํ•˜๋Š” ์‹ค์ˆ˜

โŒ ์‹ค์ˆ˜ 1: ํ…Œ์ŠคํŠธ๋ฅผ ๋„ˆ๋ฌด ํฌ๊ฒŒ ์“ด๋‹ค

1
2
3
4
5
6
7
8
// ๋‚˜์œ ์˜ˆ โ€” ํ•œ ๋ฒˆ์— ๋ชจ๋“  ๊ฒฝ์šฐ๋ฅผ ํ…Œ์ŠคํŠธ
void FizzBuzz_์ „์ฒด_๋™์ž‘_ํ…Œ์ŠคํŠธ() {
    assertThat(fizzBuzz.convert(1)).isEqualTo("1");
    assertThat(fizzBuzz.convert(3)).isEqualTo("Fizz");
    assertThat(fizzBuzz.convert(5)).isEqualTo("Buzz");
    assertThat(fizzBuzz.convert(15)).isEqualTo("FizzBuzz");
    // ... 10์ค„ ๋”
}
1
2
3
4
// ์ข‹์€ ์˜ˆ โ€” ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ, ํ•˜๋‚˜์˜ ๋™์ž‘
void ์ˆซ์ž_3์€_Fizz๋ฅผ_๋ฐ˜ํ™˜ํ•œ๋‹ค() {
    assertThat(fizzBuzz.convert(3)).isEqualTo("Fizz");
}

โŒ ์‹ค์ˆ˜ 2: GREEN์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ์™„๋ฒฝํ•œ ์ฝ”๋“œ๋ฅผ ์“ฐ๋ ค ํ•œ๋‹ค

TDD์—์„œ GREEN ๋‹จ๊ณ„๋Š” โ€œ๋™์ž‘ํ•˜๊ฒŒโ€๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
ํ•˜๋“œ์ฝ”๋”ฉ๋„ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ์ •๋ฆฌ๋Š” REFACTOR์—์„œ ํ•ฉ๋‹ˆ๋‹ค.


โŒ ์‹ค์ˆ˜ 3: REFACTOR๋ฅผ ์ƒ๋žตํ•œ๋‹ค

Red โ†’ Green๋งŒ ๋ฐ˜๋ณตํ•˜๊ณ  Refactor๋ฅผ ๊ฑด๋„ˆ๋›ฐ๋ฉด ์ฝ”๋“œ๊ฐ€ ์ ์  ์ง€์ €๋ถ„ํ•ด์ง‘๋‹ˆ๋‹ค.
Green์ด ๋˜๋ฉด ๋ฐ˜๋“œ์‹œ ์ฝ”๋“œ๋ฅผ ์ •๋ฆฌํ•˜๋Š” ์Šต๊ด€์„ ๋“ค์ด์„ธ์š”.


โŒ ์‹ค์ˆ˜ 4: ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•œ ๋’ค ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ๋ฅผ ๋” ๊ฑด๋“œ๋ฆฐ๋‹ค

Green์ด ๋์œผ๋ฉด ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ํ…Œ์ŠคํŠธ๋กœ ๋„˜์–ด๊ฐ€์„ธ์š”.
โ€œ์–ด์ฐจํ”ผ ๋‚˜์ค‘์— ํ•„์š”ํ•  ๊ฒƒ ๊ฐ™์•„์„œโ€ฆโ€ ๋ผ๋Š” ์ƒ๊ฐ์œผ๋กœ ๋ฏธ๋ฆฌ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ๋งˆ์„ธ์š”.


โŒ ์‹ค์ˆ˜ 5: ์ฒ˜์Œ๋ถ€ํ„ฐ ์–ด๋ ค์šด ๋ฌธ์ œ์— ๋„์ „ํ•œ๋‹ค

TDD ๊ทผ์œก์„ ํ‚ค์šฐ๋ ค๋ฉด ๋ฐ˜๋ณต ํ›ˆ๋ จ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
FizzBuzz, String Calculator ๊ฐ™์€ ์‰ฌ์šด ๋ฌธ์ œ๋ฅผ ํ•˜๋ฃจ 15๋ถ„์”ฉ ํ•œ ๋‹ฌ๊ฐ„ ๋ฐ˜๋ณตํ•˜๋Š” ๊ฒƒ์ด
์–ด๋ ค์šด ๋ฌธ์ œ ํ•˜๋‚˜๋ฅผ ํ•œ ๋ฒˆ ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ํ›จ์”ฌ ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.


7. ์˜ค๋Š˜ ๋‹น์žฅ ์‹œ์ž‘ํ•˜๋Š” ๋ฐฉ๋ฒ•

๐Ÿš€ 5๋ถ„ ์•ˆ์— ์‹œ์ž‘ํ•˜๊ธฐ

  1. https://cyber-dojo.org ์ ‘์†
  2. [I practice] ํด๋ฆญ
  3. Java ๋˜๋Š” Go ์„ ํƒ
  4. FizzBuzz ์„ ํƒ
  5. ํ…Œ์ŠคํŠธ ํŒŒ์ผ ์—ด๊ธฐ โ†’ ์ฒซ ๋ฒˆ์งธ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ
  6. [test] ๋ฒ„ํŠผ โ†’ ๋นจ๊ฐ„๋ถˆ(RED) ํ™•์ธ
  7. ๊ตฌํ˜„ ํŒŒ์ผ ์—ด๊ธฐ โ†’ ์ตœ์†Œ ์ฝ”๋“œ ์ž‘์„ฑ
  8. [test] ๋ฒ„ํŠผ โ†’ ์ดˆ๋ก๋ถˆ(GREEN) ํ™•์ธ
  9. ๋‹ค์Œ ํ…Œ์ŠคํŠธ ์ž‘์„ฑโ€ฆ

๐Ÿ“… ํ•œ ๋‹ฌ ์—ฐ์Šต ๊ณ„ํš

์ฃผ์ฐจ๋ชฉํ‘œ์นดํƒ€
1์ฃผ์ฐจRed โ†’ Green ๋ฆฌ๋“ฌ ์ตํžˆ๊ธฐFizzBuzz ๋งค์ผ ๋ฐ˜๋ณต
2์ฃผ์ฐจ์ž‘๊ฒŒ ์ชผ๊ฐœ๋Š” ์Šต๊ด€String Calculator (Step 1~3)
3์ฃผ์ฐจRefactor ์Šต๊ด€ํ™”String Calculator (Step 4~7)
4์ฃผ์ฐจ์‹ค์ œ ํ”„๋กœ์ ํŠธ ์ ์šฉ ์‹œ๋„๋ณธ์ธ ํ”„๋กœ์ ํŠธ์˜ ์œ ํ‹ธ ํ•จ์ˆ˜ 1๊ฐœ

๐Ÿ’ก ํ•ต์‹ฌ: ๋งค์ผ 15๋ถ„์ด ํ•œ ๋‹ฌ์— ํ•œ ๋ฒˆ 4์‹œ๊ฐ„๋ณด๋‹ค ํ›จ์”ฌ ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.
๋‡Œ๋Š” ์งง์€ ๋ฐ˜๋ณต์„ ํ†ตํ•ด ์ƒˆ๋กœ์šด ์Šต๊ด€์„ ๋งŒ๋“ค์–ด ๋ƒ…๋‹ˆ๋‹ค.


๐Ÿ’ฌ ๋งˆ์ง€๋ง‰์œผ๋กœ

10๋…„๊ฐ„ TDD๋ฅผ ์ดํ•ดํ•˜๋ ค ํ–ˆ์ง€๋งŒ ์‹ค์ฒœํ•˜์ง€ ๋ชปํ•œ ๊ฑด ์˜์ง€์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.
TDD๋Š” ์ฝ์–ด์„œ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์†์œผ๋กœ ๋ฐ˜๋ณตํ•ด์•ผ ๋ชธ์ด ๊ธฐ์–ตํ•˜๋Š” ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค.

์˜ค๋Š˜ FizzBuzz ํ•˜๋‚˜๋งŒ Cyber-Dojo์—์„œ Red โ†’ Green โ†’ Refactor ์‚ฌ์ดํด๋กœ ์™„์„ฑํ•ด ๋ณด์„ธ์š”.
๊ทธ 15๋ถ„์ด ์ง€๋‚œ 10๋…„์˜ ๋ฒฝ์„ ๋ฌด๋„ˆ๋œจ๋ฆฌ๋Š” ์ฒซ ๋ฒˆ์งธ ๋ฒฝ๋Œ์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.


์ฐธ๊ณ : Uncle Bob (Robert C. Martin)์˜ TDD 3๋ฒ•์น™, Clean Code, Cyber-Dojo, Exercism, Coding Dojo

์ด ๊ธฐ์‚ฌ๋Š” ์ €์ž‘๊ถŒ์ž์˜ CC BY 4.0 ๋ผ์ด์„ผ์Šค๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

ยฉ BLUEBUG. ์ผ๋ถ€ ๊ถŒ๋ฆฌ ๋ณด์œ 

Powered by Jekyll with Chirpy theme