์ฌ๊ธฐ ๋งํฌ์ ์๋ ๋ฐฑ๋ช
์๋์ README๋ฅผ ๊ทธ๋๋ก ๋ณต์ฌํด์๋ค.
๊ตฌํ ๋ฐฉ๋ฒ๋ ๊ฐ๋ค. ๋์ TypeScript๋ก ์์ฑํ๋ค.
์ด ํ๋ก์ ํธ์์๋ ๋ฐฅ์์ ์จ๋ฅผ ๋น๋กฏํ ๋ง์ ์ฌ๋๋ค์ด 10์ฌ๊ฐ TDD๋ฅผ ์ค๋ช ํ๊ธฐ ์ํ ์์ ๋ก ์ฌ์ฉํ Bowling Game์ TDD๋ก ๊ตฌํํ๋ ๊ฒ์ ์ค๋ช ํ๊ณ , TDD์ ๋ํ ๋ฐ๋ฐ๊ณผ ์ด์ ๋ํ ๋ต๋ณ์ ๋ํด ์์๋ณธ๋ค.
- ๊ท์น
- ๋ณผ๋ง ๊ฒ์์ 10๊ฐ์ ํ๋ ์์ผ๋ก ๊ตฌ์ฑ๋๋ค.
- ๊ฐ ํ๋ ์์ ๋๊ฐ 2 ๋กค์ ๊ฐ๋๋ค(10๊ฐ์ ํ์ ์ฐ๋ฌ ๋จ๋ฆฌ๊ธฐ ์ํด 2๋ฒ์ ๊ธฐํ๋ฅผ ๊ฐ๋๋ค).
- Spare: 10 + next first roll์์ ์ฐ๋ฌ ๋จ๋ฆฐ ํ์.
- Strike: 10 + next two rolls์์ ์ฐ๋ฌ ๋จ๋ฆฐ ํ์.
- 10th ํ๋ ์์ ํน๋ณ. spare ์ฒ๋ฆฌํ๋ฉด 3๋ฒ ๋์ง ์ ์์.
Game์ด๋ผ๋ ํด๋์ค๋ฅผ ์์ฑํ๋ ๊ฒ์ด ์ด ์์ ์ ๋ชฉ์ ์ด๋ค.
- Game ํด๋์ค๋
- roll๊ณผ score๋ผ๋ 2๊ฐ์ ๋ฉ์๋๋ฅผ ๊ฐ๋๋ค.
- roll ๋ฉ์๋๋ ball์ rollํ ๋๋ง๋ค ํธ์ถ๋๋ค. ์ธ์๋ก๋ ์ฐ๋ฌ๋จ๋ฆฐ ํ์๋ฅผ ๊ฐ๋๋ค.
- score ๋ฉ์๋๋ ๊ฒ์์ด ๋๋ ํ์๋ง ํธ์ถ๋์ด ๊ฒ์์ ์ ์๋ฅผ ๋ฐํํ๋ค.
- ๊ฒ์์ 10๊ฐ์ ํ๋ ์์ ๊ฐ๋๋ค.
- Game์ roll, score ํจ์๋ฅผ ๊ฐ๋๋ค.
- Game์ 10๊ฐ์ Frame์ ๊ฐ๋๋ค.
- Frame์ 1..2๊ฐ์ Roll์ ๊ฐ๋๋ค.
- 10๋ฒ ํ๋ ์์ ์์ธ๋ฅผ ๊ฐ๋๋ค(1..2 roll์ ๊ฐ๋ ๊ฒ์ด ์๋๋ผ 2..3 roll์ ๊ฐ๋๋ค).
- ๊ฐ์ฒด์งํฅ์์ ์ด๋ฐ ์์ธ๋ฅผ ์ด๋ป๊ฒ ํํํ๋ ???
** score ํจ์์ ์๊ณ ๋ฆฌ์ฆ
frame์๋งํผ loop๋ฅผ ๋๋ฉด์ ๊ฐ frame์ ์ ์๋ฅผ ํฉ์ฐํ ๊ฒ์ด๋ค.
frame์ roll์ ๋งํผ loop๋ฅผ ๋๋ฉด์ ์ ์๋ฅผ ๊ณ์ฐํ ๊ฒ์ด๋ค. strike, spare๋ฅผ ์ํด์ look ahead roll์ ํด์ผ ํ๋ค.
TDD์์ ์ด์ํ ์ผ์ ํ๋ค. ์ค๊ณ๋ฅผ ๋ฌด์ํ๋ ๊ฒ์ด๋ค. ์์ ํ ๋ฌด์ํ๋ ๊ฒ์ ์๋๊ณ ๋ฐ๋ฅด์ง ์๋ ๊ฒ์ด๋ค. ๋จ์ง ๊ฐ์ด๋ ๋ผ์ธ์ ์ผ์ ์ผ๋ก ์ฌ์ฉํ๋ค.
public class BowlingTest {
@Test
public void nothing() {
}
}
- ์๋ฌด๊ฒ๋ ์๋ nothing์ด๋ผ๋ ํ ์คํธ๋ก ์์. ์ด๊ฒ๋ ์คํํด ๋ณธ๋ค. ๋ฐฅ ์์ ์จ๋ ํญ์ ๋ญ๊ฐ ์คํ๋๋ ๊ฒ์ผ๋ก ์์ํ๋ค๊ณ ํ๋ค. ๊ทธ๋์ ์ฌ์ง์ด ์๋ฏธ ์๋ ์ฝ๋๊ฐ ํ๋๋ ์๋๋ผ๋ ์คํ๋๋ ๋ญ๊ฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ค.
- ๊ทธ๋ฆฌ๊ณค ์ง์ด๋ค. ์๋ง ํ ์คํธ ์์ฑ์ ์ํ ์ค์ ์ด ์ ๋๋ก ๋์๋์ง ํ์ธํ๋๊ฐ๋ณด๋ค. ๋์ค์ ์์์ง๋ง ํญ์ ๋์ํ๋ ์ฝ๋๋ก ์์ ํ๊ธฐ ์ํด์์ด๋ค. ์ด๊ฒ ๊ฐ๋ฐ์๋ค์๊ฒ๋ ํธ์ํจ์ ์ค๋ค.
- ๋ฌด์จ ํ
์คํธ๋ฅผ ์์ฑํด์ผ ํ๋ ?
- failing unit test๊ฐ ์๊ธฐ ์ ์๋ production code๋ฅผ ์์ฑํ๋ฉด ์๋๋ค.
- ํ์ง๋ง ์ด๋ฏธ ๊ฐ๋ฐ์๋ ์ด๋ค production code๋ฅผ ์์ฑํด์ผ ํ๋์ง ์๋ค. public class Game์ ์์ฑํด์ผ ํ๋ค๋ ๊ฒ์ ...
- ๊ทธ๋ฌ๋ ์ฐ๋ฆฌ๋ public class Game์ ์์ฑํ๋๋ก ํ๊ฐ ๋ฐ์ง ์์๋ค. ์ฐ๋ฆฌ๋ ์ ๋ ํ ์คํธ๋ฅผ ๋จผ์ ์์ฑํด์ผ ํ๋ค.
- ์ด๋ค ํ
์คํธ๋ฅผ ์์ฑํด์ผ ๋ด๊ฐ ์ํ๋ ์ฝ๋๋ฅผ ์์ฑํ๊ฒ ๋ ๊น ?
- ์ด๋ค failing test๋ฅผ ์์ฑํด์ผ Game.java์ public class Game์ ์ ์ธํ๊ฒ ๋ ๊น ?
- ์ด์ ๋ถํฐ red-green-refactor cycle๋ก ๋ค์ด๊ฐ์.
- red phase: next most interesting case but still really simple
- green phase: make it pass
- blue phase: refactor
- ๊ฐ์ฅ ์ฝ๊ณ (๊ฐ๋จํ๊ณ ) ํฅ๋ฏธ๋ก์ด(easy/simple and interesting) ํ ์คํธ๋ถํฐ ์์ฑ
@Test
public void canCreateGame() {
Game g = new Game();
}
- IDE์ hot fix๋ฅผ ์ด์ฉ(F2: next error, opt+enter: hot fix)
- ์ค์ฝ์ด๋ฅผ ๋ฐ๋ก ๊ณ์ฐํ๊ธฐ ๋ณด๋ค๋ ์ด๋ฅผ ์ํ ๊ณผ์ ์ผ๋ก canRoll์ ๋จผ์ ์ถ๊ฐ
- ๋์ด์ง pin์๊ฐ 0์ธ ๊ฒ์ ๋ํ failing test๋ฅผ ๋จผ์ ์ถ๊ฐ
@Test
public void canRoll() {
Game g = new Game();
g.roll(0);
}
- make it pass by using hot fix
- ํ
์คํธ์ ์๋ ์ค๋ณต(
Game game = new Game();
) ์ ๊ฑฐ - ํด๋น ์ฝ๋๋ฅผ extract field(initialize๋ setUp์ ์ ํ)
- ๋ถํ์ํ ์ฝ๋ ์ ๊ฑฐ(
@Test public void canCreate()
๋ฉ์๋๋ ๋ถํ์)
- score๋ฅผ ๋ฐ๋ก ํธ์ถํ๊ณ ์ถ์ง๋ง, ๊ฒ์์ด ๋๋์ผ๋ง scoreํจ์๋ฅผ ํธ์ถํ ์ ์๋ค.
- ๊ฒ์์ ๋๋ด๋ ๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ gutter game์ด๋ค.
@Test
public void gutterGame() {
for(int i = 0; i < 20; i++)
game.roll(0);
assertThat(game.score(), is(0));
}
- next most simple and interesting test case
@Test
public void allOnes() {
for(int i = 0; i < 20; i++)
game.roll(1);
assertThat(game.score(), is(20));
}
private Integer score = 0;
public void roll(int pins) {
score += pins;
}
public Integer score() {
return score;
}
- extract variable
- gutterGame()์์ rolls, pins๋ฅผ ์ถ์ถ
@Test
public void gutterGame() {
int rolls = 20;
int pins = 0;
for(int i = 0; i < rolls; i++) {
game.roll(pins);
}
assertThat(game.score(), is(0));
}
- extract method - rollMany()
@Test
public void gutterGame() {
int rolls = 20;
int pins = 0;
rollMany(rolls, pins);
assertThat(game.score(), is(0));
}
private void rollMany(int rolls, int pins) {
for(int i = 0; i < rolls; i++)
game.roll(pins);
}
- inline variables
@Test
public void gutterGame() {
rollMany(20, 0);
assertThat(game.score(), is(0));
}
- gutter, allOne์ด ์์ผ๋ allTwo๋ฅผ ์๊ฐํด ๋ณผ ์ ์์ผ๋ ์ด๊ฑด ์ ๋์ํ ๊ฒ์ด๋ค.
- ๋ปํ ๋์ํ ๊ฒ์ ์ ์ ์๋ ํ ์คํธ๋ ์์ฑํ ํ์๊ฐ ์๋ค.
- allThree, allFour๋ ์ ๋์ํ ๊ฒ์ด๋ค. ๊ทธ๋ฐ๋ฐ allFive๋ ๊ทธ๋ ์ง ์๋ค. spare๊ฐ ์๊ธฐ ๋๋ฌธ์.
- spare์ ๋ํ ํ ์คํธ๋ฅผ ์์ฑํ ์ฐจ๋ก์ด๋ค. ๊ฐ์ฅ ๊ฐ๋จํ spare๋ ์ด๋ค ๊ฒฝ์ฐ๊ฐ ์์๊น ? one spare + gutter.
@Test
public void oneSpare() {
game.roll(5);
game.roll(5); // spare
game.roll(3);
rollMany(17, 0);
assertThat(game.score(), is(16));
}
- ๊ทผ๋ฐ ์ด๋ป๊ฒ ํด์ผ ํ ์ง ๋ชจ๋ฅด๊ฒ ๋ค.
public void roll(int pins) {
if(pins + lastPins == 10)
...
}
- ์์ ๊ฐ์ด ํ๋ ค๋ค ๋ณด๋ ์ด์ํ๋ค. ํ๋๊ทธ ๋ณ์, ์ ์ ๋ณ์๋ฅผ ์ฌ์ฉํด์ผ ํ๊ณ โฆ
- ์ด์ฒ๋ผ ๋์ฐํ ์ผ์ ํด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ์๊ธธ๋๋ง ์ ์ ๋ฌผ๋ฌ๋์ผ ํ๋ค.
- ๋ญ๊ฐ ๋์์ธ์ด ์ ๋ชป๋ ๊ฒ์ด๋ค.
- ๋์์ธ ์์น์ด ์๋ฐฐ๋ ๊ฒ์ด ์๋ค.
- ์ฒซ๋ฒ์งธ ์์น: ์ค์ฝ์ด๋ฅผ ๊ณ์ฐํ๋ ๊ฒ์ ์๋ฏธํ๋ ์ด๋ฆ์ ๊ฐ๋ ํจ์๊ฐ ๋ฌด์์ธ๊ฐ ?
- score ํจ์์ด๋ค. ๊ทผ๋ฐ ์ค์ ๋ก score๋ฅผ ๊ณ์ฐํ๋ ํจ์๋ roll ํจ์์ด๋ค.
- ์๋ชป๋ ์ฑ ์ ํ ๋น(misplaced responsibility)์ด ๋์์ธ ์์น, ์๋ชป๋ ๋์์ธ ๋์์ด๋ค.
- roll์์ ๊ฐ roll์ ์ ์ฅํ๊ณ , score์์ ๊ณ์ฐ์ ํด์ผ ํ๋ค.
- ์ด์ฉ์ง. refactoring. ๊ทผ๋ฐ failing test๊ฐ ์๋ค. @Ignore ์ฒ๋ฆฌโฆ
- ์ด์ ํ ์คํธ๊ฐ ์ํ๋๋ ๋ฆฌํฉํ ๋งํ์.
- Roll์ ๋ฐฐ์ด์ ์ ์ฅํ์.
- ์ด์ ์๋ชป๋ ์ฑ ์ ํ ๋น์ด ํด์๋์๋ค.
public class Game {
private int[] rolls = new int[21];
private int currentRoll = 0;
public void roll(int pins) {
rolls[currentRoll++] = pins;
}
public Integer score() {
int score = 0;
for(int i = 0; i < rolls.length; i++)
score += rolls[i];
return score;
}
}
- frame์ ๋์ ํ์ฌ ์ฝ๊ธฐ ์ฝ๊ฒํ๋ค.
public Integer score() {
int score = 0;
int i = 0;
for(int frame = 0; frame < 10; frame++) {
score += rolls[i] + rolls[i + 1];
i += 2;
}
return score;
}
- @Ignore ์ ๊ฑฐ
if(rolls[i] + rolls[i + 1] == 10) { // spare
score += 10 + rolls[i + 2];
i += 2;
}
else {
score += rolls[i] + rolls[i + 1];
i += 2;
}
- rename i to firstFrame
- ์ฒ์๋ถํฐ firstFrame์ด๋ผ๋ ๋ณ์๋ช ์ ์ฐพ์ ๊ฒ์ด ์๋๋ผ ์ฌ๋ฌ์ฐจ๋ก์ ์๋๋ก ์ฐพ์
- extract method isSpare
- remove duplication - comment // spare
- extract method for readability(rollSpare)
@Test
public void oneSpare() {
rollSpare();
game.roll(3);
rollMany(17, 0);
assertThat(game.score(), is(16));
}
private void rollSpare() {
game.roll(5);
game.roll(5);
}
@Test
public void oneStrike() {
game.roll(10);
game.roll(5);
game.roll(3);
rollMany(16, 0);
assertThat(game.score(), is(26));
}
if(rolls[firstFrame] == 10) { // strike
score += 10 + rolls[firstFrame + 1] + rolls[firstFrame + 2];
firstFrame += 1;
}
else if(isSpare(firstFrame)) {
...
- extract method isStrike
- extract method for readabiliy(nextTwoBallsForStrike, nextBallForSpare, nextBallsInFrame)
if(isStrike(firstFrame)) {
score += 10 + nextTwoBallsForStrike(firstFrame);
firstFrame += 1;
}
else if(isSpare(firstFrame)) {
score += 10 + nextBallForSpare(firstFrame);
firstFrame += 2;
}
else {
score += nextBallsInFrame(firstFrame);
firstFrame += 2;
}
- extract method for readabiliy(rollStrike)
@Test
public void oneStrike() {
rollStrike();
game.roll(5);
game.roll(3);
rollMany(16, 0);
assertThat(game.score(), is(26));
}
private void rollStrike() {
game.roll(10);
}
@Test
public void perfectGame() {
rollMany(12, 10);
assertThat(game.score(), is(300));
}
- red phase์ธ๋ฐ ํ ์คํธ๊ฐ ์ฑ๊ณตํ๋ค. ์ ๊ทธ๋ด๊น ?
- production code๋ฅผ ์ดํด๋ณด๋ 3๊ฐ์ ๊ฒฝ์ฐ ์๊ฐ ์๋๋ฐ, ์ค์ ๋ณผ๋ง ๊ฒ์์๋ ๊ทธ 3๊ฐ์ง ๊ฒฝ์ฐ ์๋ง ์กด์ฌํ๋ค.