๐ด๐ขโป๏ธ TDD ์ ๋ฌธ ๊ฐ์ด๋ โ 10๋ ์งธ ๋ฒฝ ์์ ์ ๋น์ ์ ์ํด
โ์คํจํ๋ ํ ์คํธ๊ฐ ์์ผ๋ฉด ํ๋ก๋์ ์ฝ๋๋ฅผ ํ ์ค๋ ์ฐ์ง ์๋๋ค.โ
โ Robert C. Martin (Uncle Bob)
๐ ๋ชฉ์ฐจ
- TDD๋ ๋ฌด์์ธ๊ฐ โ 5๋ถ ์์ฝ
- Uncle Bob์ 3๊ฐ์ง ๋ฒ์น
- Red โ Green โ Refactor ์ฌ์ดํด
- ์ฒ์ ํด๋ณด๋ TDD ์ค์ ์๋ฎฌ๋ ์ด์
- TDD ์ฐ์ต ์ฌ์ดํธ & ๋๊ตฌ
- ์ ๋ฌธ์๊ฐ ๊ฐ์ฅ ๋ง์ด ํ๋ ์ค์
- ์ค๋ ๋น์ฅ ์์ํ๋ ๋ฐฉ๋ฒ
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๋ช ๋์)
์ฌ์ฉ ๋ฐฉ๋ฒ:
- https://cyber-dojo.org ์ ์
[I practice]๋ฒํผ ํด๋ฆญ- ์ธ์ด(Java ๋๋ Go) + ํ ์คํธ ํ๋ ์์ํฌ ์ ํ
- ๋ฌธ์ ์ ํ (FizzBuzz ๊ถ์ฅ) โ
[start] - ํ
์คํธ ํ์ผ์ ๋จผ์ ์์ โ
[test]๋ฒํผ์ผ๋ก ์คํ - ์๋จ์ ํธ๋ํฝ ๋ผ์ดํธ ์์์ผ๋ก 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 Buddy | https://www.tddbuddy.com | ๊ฐ์ด๋ + ์นดํ ๋ชจ์, ์ด๋ณด์ ์นํ์ |
| Kata-log | https://kata-log.rocks/tdd | ๋์ด๋ยทํ๊ทธ๋ณ ์นดํ ๋ถ๋ฅ |
| Roy Osherove String Calculator | https://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๋ถ ์์ ์์ํ๊ธฐ
- https://cyber-dojo.org ์ ์
[I practice]ํด๋ฆญ- Java ๋๋ Go ์ ํ
FizzBuzz์ ํ- ํ ์คํธ ํ์ผ ์ด๊ธฐ โ ์ฒซ ๋ฒ์งธ ํ ์คํธ ์์ฑ
[test]๋ฒํผ โ ๋นจ๊ฐ๋ถ(RED) ํ์ธ- ๊ตฌํ ํ์ผ ์ด๊ธฐ โ ์ต์ ์ฝ๋ ์์ฑ
[test]๋ฒํผ โ ์ด๋ก๋ถ(GREEN) ํ์ธ- ๋ค์ ํ ์คํธ ์์ฑโฆ
๐ ํ ๋ฌ ์ฐ์ต ๊ณํ
| ์ฃผ์ฐจ | ๋ชฉํ | ์นดํ |
|---|---|---|
| 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