I'll write this article to work on TDD and to share with others an essential image of TDD.
Design / analysis techniques, not testing techniques
--Value --Development method that promotes incremental design —— Create a good rhythm for your team --Bring confidence and trust in development --Principle --Create a test before implementing --To move forward little by little without leaving the problem alone ――The important thing is to stay confident rather than perfection --Rules -Write new code only if the (automated) test fails --Remove duplicates
――To make something valuable as efficiently as possible ――Because the more you understand the development target, the more correct decisions you can make. ――I won't decide until I can make the right decision about what to make --While gradually developing from the minimum required functions and gradually evolving the design itself -** By gradually increasing the level of understanding of the development target, you will be able to make correct decisions **
Suitable case: Agile ・ I don't know what I want to make ・ The development team does not fully understand the development target ・ Development target is complicated and no one knows the correct design
--Red: Write a test that fails (addition of functions / addition of conditions / bug reproduction etc.) --Green: Successful test (you may commit crimes such as writing a minimum test or writing duplicate code) --Refactor: Refine the design while maintaining its behavior (deduplication written to pass the test)
By rotating the above cycle, we will design incrementally based on rhythmic, self-confidence and trust.
--Write the required tests on the TODO list --Write down the tests that you might need before you start (only those that you can recognize at the moment) ――Distribute to 3 lists of "I will do it immediately", "I will do it later", and "I do not need to do it at all" --Write down as many behaviors you need to implement as you can, and add operations that are not yet implemented to the list ――Write out a refactoring to clean up the code you just wrote --If you come up with a new test, write it on the list. Same for refactoring --Features should not be added until they are actually needed ――I'm sure you will need it later → 90% will be wasted --Adding more features than you need now complicates your design ――Adding unnecessary functions only takes away resources such as time for other members to read and description in documents. --Fast code and reduce bugs --The best way is to keep the code you implement to the bare minimum --Sophisticate design by going back and forth between static design and dynamic design --Static design (design responsibility)
Subject: Bowling score calculation usage environment: --IntelliJ Community version --Java 11 * Sorry for being old --Gradle * For acquisition / management of the two libraries below and execution of build
The first test to add is to write a "do nothing test"
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
@Test
public void test_all_Garter() {
var game = new BowlingGame();
assertThat(game).isNotNull();
}
}
Green
Add class
public class BowlingGame {
}
Red
Make it a test case for all pitching garters
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
@Test
public void test_all_Garter() {
var game = new BowlingGame();
+ for(int count = 0; count < 20; count++) {
+ game.recordOneShot(0);
+ }
- assertThat(game).isNotNull();
+ assertThat(game.getTotalScore()).isEqualTo(0);
}
}
Green
public class BowlingGame {
+ public void recordOneShot(int pins) {
+ }
+ public int score() {
+ return 0;
+ }
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
// ry
+ @Test
+ public void test_all_one_pin() {
+ var game = new BowlingGame();
+ for (int count = 0; count < 20; count++) {
+ game.recordOneShot(1);
+ }
+ assertThat(game.score()).isEqualTo(20);
+ }
}
Green
public class BowlingGame {
private int totalScore = 0;
+ public void recordOneShot(int pins) {
+ totalScore += pins;
+ }
+ public int getTotalScore() {
+ return totalScore;
+ }
}
Triggered by the "sinister smell" of the code
--Duplicate code --Methods with long lines --Too many arguments etc.
//Test code refactoring (duplicate code removal: method extraction)
import static org.assertj.core.api.Assertions.assertThat;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
+ private BowlingGame game;
+
+ @BeforeEach
+ public void setup() {
+ game = new BowlingGame();
+ }
@Test
public void test_test_all_Garter() {
- var game = new BowlingGame();
- for(int count = 0; count < 20; count++) {
- game.recordOneShot(0);
- }
+ recordSamePinsManyShot(20, 0);
assertThat(game.getTotalScore()).isEqualTo(0);
}
@Test
public void test_all_one_pin() {
- var game = new BowlingGame();
- for (int count = 0; count < 20; count++) {
- game.recordOneShot(1);
- }
+ recordSamePinsManyShot(20, 1);
assertThat(game.getTotalScore()).isEqualTo(20);
}
+ private void recordSamePinsManyShot(int shotCount, int pins) {
+ for (int count = 0; count < shotCount; count++) {
+ game.recordOneShot(pins);
+ }
+ }
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
private BowlingGame game;
@BeforeEach
public void setup() {
game = new BowlingGame();
}
// ry
+ @Test
+ public void test_spare() {
+ game.recordOneShot(4);
+ game.recordOneShot(6); //Spare 10+5=15
+ game.recordOneShot(5);
+ recordSamePinsManyShot(17, 0);
+ assertThat(game.getTotalScore()).isEqualTo(20);
+ }
private void recordSamePinsManyShot(int shotCount, int pins) {
for (int count = 0; count < shotCount; count++) {
game.recordOneShot(pins);
}
}
}
--Think about how to fix --Think about the cause → Because I don't remember the previous pitching state --Consider a workaround → Prepare a spare flag -* At this time, do not think about other problems (strike, 10th frame, etc.) Incrementally
--Think about the procedure --Addition of spare flag --Total points are 10 points → Flag is ON, if flag is ON, add
↓ Try to fix
public class BowlingGame {
private int totalScore = 0;
+ private boolean isSpare = false;
public void recordOneShot(int pins) {
totalScore += pins;
+ if (isSpare) {
+ totalScore += pins;
+ isSpare = false;
+ }
+ if (totalScore == 10) {
+ isSpare = true;
+ }
}
public int getTotalScore() {
return totalScore;
}
}
--Fixed to pass if existing test fails --Cause → Because the flag was turned on when the total score was 10. --Workaround → Change condition: Flag ON when the total with the number of pins before 1 time reaches 10.
package bowling;
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
+ private int beforePins = 0;
public void recordOneShot(int pins) {
totalScore += pins;
if (isSpare) {
totalScore += pins;
isSpare = false;
}
- if (totalScore == 10) {
+ if (pins + beforePins == 10) {
isSpare = true;
}
+ beforePins = pins;
}
public int getTotalScore() {
return totalScore;
}
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
private BowlingGame game;
@BeforeEach
public void setup() {
game = new BowlingGame();
}
// ry
+ @Test
+ public void test_not_spare_when_before_and_current_total_10() {
+ game.recordOneShot(2);
+ game.recordOneShot(6);
+ game.recordOneShot(4);
+ game.recordOneShot(7);
+ recordSamePinsManyShot(16, 0);
+ assertThat(game.getTotalScore()).isEqualTo(19);
+ }
private void recordSamePinsManyShot(int shotCount, int pins) {
for (int count = 0; count < shotCount; count++) {
game.recordOneShot(pins);
}
}
}
Green
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
private int beforePins = 0;
private int shotCount = 1;
public void recordOneShot(int pins) {
totalScore += pins;
if (isSpare) {
totalScore += pins;
isSpare = false;
}
- if (pins + beforePins == 10) {
+ if (shotCount == 2 && pins + beforePins == 10) {
isSpare = true;
}
beforePins = pins;
+ shotCount = shotCount == 1 ? 2 : 1;
}
public int getTotalScore() {
return totalScore;
}
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
private BowlingGame game;
@BeforeEach
public void setup() {
game = new BowlingGame();
}
// ry
+ @Test
+ public void test_strike() {
+ game.recordOneShot(10);
+ game.recordOneShot(3);
+ game.recordOneShot(4);
+ game.recordOneShot(2);
+ recordSamePinsManyShot(15, 0);
+ assertThat(game.getTotalScore()).isEqualTo(26);
+ }
private void recordSamePinsManyShot(int shotCount, int pins) {
for (int count = 0; count < shotCount; count++) {
game.recordOneShot(pins);
}
}
}
--Fixed --Cause: Strike cannot be determined --Workaround: Set the number of points to be added after the current shot
Green
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
private int beforePins = 0;
private int shotCount = 1;
+ private int strikeAddScoreCount = 0;
public void recordOneShot(int pins) {
totalScore += pins;
if (isSpare) {
totalScore += pins;
isSpare = false;
}
if (shotCount == 2 && pins + beforePins == 10) {
isSpare = true;
}
+ if (strikeAddScoreCount > 0) {
+ totalScore += pins;
+ strikeAddScoreCount -= 1;
+ }
+ if (pins == 10) {
+ strikeAddScoreCount = 2;
+ }
beforePins = pins;
shotCount = shotCount == 1 ? 2 : 1;
}
public int getTotalScore() {
return totalScore;
}
}
Method extraction
package bowling;
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
private int beforePins = 0;
private int shotCount = 1;
private int strikeAddScoreCount = 0;
public void recordOneShot(int pins) {
totalScore += pins;
- if (isSpare) {
- totalScore += pins;
- isSpare = false;
- }
- if (shotCount == 2 && pins + beforePins == 10) {
- isSpare = true;
- }
-
- if (strikeAddScoreCount > 0) {
- totalScore += pins;
- strikeAddScoreCount -= 1;
- }
- if (pins == 10) {
- strikeAddScoreCount = 2;
- }
+ calcSpareAddScore(pins);
+ calcStrikeAddScore(pins);
beforePins = pins;
shotCount = shotCount == 1 ? 2 : 1;
}
+ public int getTotalScore() {
+ return totalScore;
+ }
+
+ private void calcSpareAddScore(int pins) {
+ if (isSpare) {
+ totalScore += pins;
+ isSpare = false;
+ }
+ if (shotCount == 2 && pins + beforePins == 10) {
+ isSpare = true;
+ }
+ }
+
+ private void calcStrikeAddScore(int pins) {
+ if (strikeAddScoreCount > 0) {
+ totalScore += pins;
+ strikeAddScoreCount -= 1;
+ }
+ if (pins == 10) {
+ strikeAddScoreCount = 2;
+ }
+ }
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
private BowlingGame game;
@BeforeEach
public void setup() {
game = new BowlingGame();
}
// ry
+ @Test
+ public void test_strike_double() {
+ game.recordOneShot(10);
+ game.recordOneShot(10);
+ game.recordOneShot(4);
+ game.recordOneShot(2);
+ recordSamePinsManyShot(14, 0);
+ assertThat(game.getTotalScore()).isEqualTo(46);
+ }
private void recordSamePinsManyShot(int shotCount, int pins) {
for (int count = 0; count < shotCount; count++) {
game.recordOneShot(pins);
}
}
}
Green
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
private int beforePins = 0;
private int shotCount = 1;
private int strikeAddScoreCount = 0;
+ private int doubleAddScoreCount = 0;
public void recordOneShot(int pins) {
totalScore += pins;
calcSpareAddScore(pins);
calcStrikeAddScore(pins);
beforePins = pins;
shotCount = shotCount == 1 ? 2 : 1;
}
public int getTotalScore() {
return totalScore;
}
private void calcSpareAddScore(int pins) {
if (isSpare) {
totalScore += pins;
isSpare = false;
}
if (shotCount == 2 && pins + beforePins == 10) {
isSpare = true;
}
}
private void calcStrikeAddScore(int pins) {
if (strikeAddScoreCount > 0) {
totalScore += pins;
strikeAddScoreCount -= 1;
}
+ if (doubleAddScoreCount > 0) {
+ totalScore += pins;
+ doubleAddScoreCount -= 1;
+ }
if (pins == 10) {
+ if (strikeAddScoreCount == 0) {
strikeAddScoreCount = 2;
+ } else {
+ doubleAddScoreCount = 2;
+ }
}
}
}
Green
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
private BowlingGame game;
@BeforeEach
public void setup() {
game = new BowlingGame();
}
// ry
+ @Test
+ public void test_strike_turkey() {
+ game.recordOneShot(10);
+ game.recordOneShot(10);
+ game.recordOneShot(10);
+ game.recordOneShot(4);
+ game.recordOneShot(2);
+ recordSamePinsManyShot(12, 0);
+ assertThat(game.getTotalScore()).isEqualTo(76);
+ }
private void recordSamePinsManyShot(int shotCount, int pins) {
for (int count = 0; count < shotCount; count++) {
game.recordOneShot(pins);
}
}
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
private BowlingGame game;
@BeforeEach
public void setup() {
game = new BowlingGame();
}
// ry
+ @Test
+ public void test_strike_and_spare() {
+ game.recordOneShot(10);
+ game.recordOneShot(6);
+ game.recordOneShot(4);
+ game.recordOneShot(2);
+ recordSamePinsManyShot(15, 0);
+ assertThat(game.getTotalScore()).isEqualTo(34);
+ }
private void recordSamePinsManyShot(int shotCount, int pins) {
for (int count = 0; count < shotCount; count++) {
game.recordOneShot(pins);
}
}
}
--Cause: shotCount does not return to 1 after a strike --Workaround: Add condition for shotCount
Green
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
private int beforePins = 0;
private int shotCount = 1;
private int strikeAddScoreCount = 0;
private int doubleAddScoreCount = 0;
public void recordOneShot(int pins) {
totalScore += pins;
calcSpareAddScore(pins);
calcStrikeAddScore(pins);
beforePins = pins;
- shotCount = shotCount == 1 ? 2 : 1;
+ shotCount = shotCount == 1 && strikeAddScoreCount < 2 ? 2 : 1;
}
public int getTotalScore() {
return totalScore;
}
private void calcSpareAddScore(int pins) {
if (isSpare) {
totalScore += pins;
isSpare = false;
}
if (shotCount == 2 && pins + beforePins == 10) {
isSpare = true;
}
}
private void calcStrikeAddScore(int pins) {
if (strikeAddScoreCount > 0) {
totalScore += pins;
strikeAddScoreCount -= 1;
}
if (doubleAddScoreCount > 0) {
totalScore += pins;
doubleAddScoreCount -= 1;
}
if (pins == 10) {
if (strikeAddScoreCount == 0) {
strikeAddScoreCount = 2;
} else {
doubleAddScoreCount = 2;
}
}
}
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
private BowlingGame game;
@BeforeEach
public void setup() {
game = new BowlingGame();
}
// ry
+ @Test
+ public void test_strike_double_and_spare() {
+ game.recordOneShot(10);
+ game.recordOneShot(10);
+ game.recordOneShot(6);
+ game.recordOneShot(4);
+ game.recordOneShot(2);
+ recordSamePinsManyShot(13, 0);
+ assertThat(game.getTotalScore()).isEqualTo(26+20+12+2);
+ }
private void recordSamePinsManyShot(int shotCount, int pins) {
for (int count = 0; count < shotCount; count++) {
game.recordOneShot(pins);
}
}
}
--Cause: shotCount does not return to 1 even after two consecutive strikes --Workaround: Add shotCount condition as well
Green
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
private int beforePins = 0;
private int shotCount = 1;
private int strikeAddScoreCount = 0;
private int doubleAddScoreCount = 0;
public void recordOneShot(int pins) {
totalScore += pins;
calcSpareAddScore(pins);
calcStrikeAddScore(pins);
beforePins = pins;
- shotCount = shotCount == 1 && strikeAddScoreCount < 2 ? 2 : 1;
+ shotCount = shotCount == 1 && strikeAddScoreCount < 2 && doubleAddScoreCount < 2 ? 2 : 1;
}
public int getTotalScore() {
return totalScore;
}
private void calcSpareAddScore(int pins) {
if (isSpare) {
totalScore += pins;
isSpare = false;
}
if (shotCount == 2 && pins + beforePins == 10) {
isSpare = true;
}
}
private void calcStrikeAddScore(int pins) {
if (strikeAddScoreCount > 0) {
totalScore += pins;
strikeAddScoreCount -= 1;
}
if (doubleAddScoreCount > 0) {
totalScore += pins;
doubleAddScoreCount -= 1;
}
if (pins == 10) {
if (strikeAddScoreCount == 0) {
strikeAddScoreCount = 2;
} else {
doubleAddScoreCount = 2;
}
}
}
}
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
private int beforePins = 0;
private int shotCount = 1;
private int strikeAddScoreCount = 0;
private int doubleAddScoreCount = 0;
public void recordOneShot(int pins) {
totalScore += pins;
calcSpareAddScore(pins);
calcStrikeAddScore(pins);
beforePins = pins;
shotCount = shotCount == 1 && strikeAddScoreCount < 2 && doubleAddScoreCount < 2 ? 2 : 1;
}
public int getTotalScore() {
return totalScore;
}
private void calcSpareAddScore(int pins) {
if (isSpare) {
totalScore += pins;
isSpare = false;
}
checkSpare(pins);
}
private void checkSpare(int pins) {
if (shotCount == 2 && pins + beforePins == 10) {
isSpare = true;
}
}
private void calcStrikeAddScore(int pins) {
- if (strikeAddScoreCount > 0) {
- totalScore += pins;
- strikeAddScoreCount -= 1;
- }
- if (doubleAddScoreCount > 0) {
- totalScore += pins;
- doubleAddScoreCount -= 1;
- }
- if (pins == 10) {
- if (strikeAddScoreCount == 0) {
- strikeAddScoreCount = 2;
- } else {
- doubleAddScoreCount = 2;
- }
- }
+ addStrikeScore(pins);
+ addDoubleScore(pins);
+ if (isStrike(pins)) {
+ recognizeStrikeAddCount();
+ }
}
+ private void addStrikeScore(int pins) {
+ if (strikeAddScoreCount > 0) {
+ totalScore += pins;
+ strikeAddScoreCount -= 1;
+ }
+ }
+
+ private void addDoubleScore(int pins) {
+ if (doubleAddScoreCount > 0) {
+ totalScore += pins;
+ doubleAddScoreCount -= 1;
+ }
+ }
+
+ private boolean isStrike(int pins) {
+ return pins == 10;
+ }
+
+ private void recognizeStrikeAddCount() {
+ if (strikeAddScoreCount == 0) {
+ strikeAddScoreCount = 2;
+ } else {
+ doubleAddScoreCount = 2;
+ }
+ }
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
private BowlingGame game;
@BeforeEach
public void setup() {
game = new BowlingGame();
}
// ry
+ @Test
+ public void test_one_frame_score_when_all_garter() {
+ recordSamePinsManyShot(20, 0);
+ assertThat(game.getFrameScore(1)).isEqualTo(0);
+ }
private void recordSamePinsManyShot(int shotCount, int pins) {
for (int count = 0; count < shotCount; count++) {
game.recordOneShot(pins);
}
}
}
Green
public BowlingGame {
// ry
+ public int getFrameScore(int frameNum) {
+ return 0;
+ }
}
――When you try to add a test case that needs to be calculated ... Will it be repeated so far? --If you feel a stalemate, review the static design
Frame class introduced
public class Frame {
public void recordOneShot(int pins) {
}
public int getScore(int frameNum) {
return 0;
}
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class FrameTest {
private Frame frame;
@BeforeEach
public void setup() {
frame = new Frame();
}
@Test
public void test_one_frame_score_when_all_one_pin() {
frame.recordOneShot(1);
assertThat(frame.getScore(1)).isEqualTo(1);
}
}
Green
public class Frame {
private int score = 0;
public void recordOneShot(int pins) {
+ score += pins;
}
public int getScore(int frameNum) {
- return 0;
+ return score;
}
}
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
private int beforePins = 0;
private int shotCount = 1;
private int strikeAddScoreCount = 0;
private int doubleAddScoreCount = 0;
+ private Frame frame = new Frame();
// ry
+ public int getFrameScore(int frameNum) {
+ return frame.getScore(frameNum);
+ }
// ry
}
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
private BowlingGame game;
@BeforeEach
public void setup() {
game = new BowlingGame();
}
// ry
+ @Test
+ public void test_one_frame_score_when_all_garter() {
+ recordSamePinsManyShot(20, 0);
+ assertThat(game.getFrameScore(1)).isEqualTo(0);
+ }
private void recordSamePinsManyShot(int shotCount, int pins) {
for (int count = 0; count < shotCount; count++) {
game.recordOneShot(pins);
}
}
}
--Static design review: Delegate the following to the Frame class --Pitching record --Frame completion judgment --Pitching result judgment (spare / strike etc.) --Score addition judgment / record after strike / spare --Return points
MECE (Mutually Exclusive and Collectively Exhaustive): A bird's-eye view of class design from the perspective of no duplication and no omissions
Refactor by focusing only on the functions that are clear at the moment.
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class FrameTest {
private Frame frame;
@BeforeEach
public void setup() {
frame = new Frame();
}
// ry
+ @Test
+ public void test_frame_finish_two_shot() {
+ frame.recordOneShot(1);
+ assertThat(frame.isFinished()).isFalse();
+ frame.recordOneShot(1);
+ assertThat(frame.isFinished()).isTrue();
+ }
}
Green
package bowling;
public class Frame {
private int score = 0;
+ private int shotCount = 0;
public void recordOneShot(int pins) {
score += pins;
+ shotCount++;
}
public int getScore(int frameNum) {
return score;
}
+ public boolean isFinished() {
+ return shotCount >= 2;
+ }
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class FrameTest {
private Frame frame;
@BeforeEach
public void setup() {
frame = new Frame();
}
// ry
+ @Test
+ public void test_frame_finish_strike() {
+ var frame = new Frame();
+ frame.recordOneShot(10);
+ assertThat(frame.isFinished()).isTrue();
+ }
}
Green
public class Frame {
private int score = 0;
private int shotCount = 0;
public void recordOneShot(int pins) {
score += pins;
shotCount++;
}
public int getScore(int frameNum) {
return score;
}
public boolean isFinished() {
- return shotCount >= 2;
+ return shotCount >= 2 || score >= 10;
}
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
private BowlingGame game;
@BeforeEach
public void setup() {
game = new BowlingGame();
}
// ry
+ @Test
+ public void test_one_frame_score_is_2_when_all_one_pin() {
+ recordSamePinsManyShot(20, 1);
+ for (int frameNum = 0; frameNum < 10; frameNum++) {
+ assertThat(game.getFrameScore(frameNum + 1)).isEqualTo(2);
+ }
+ }
private void recordSamePinsManyShot(int shotCount, int pins) {
for (int count = 0; count < shotCount; count++) {
game.recordOneShot(pins);
}
}
}
Green
import java.util.LinkedList;
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
private int beforePins = 0;
private int shotCount = 1;
private int strikeAddScoreCount = 0;
private int doubleAddScoreCount = 0;
- private Frame frame = new Frame();
+ private final LinkedList<Frame> frames = new LinkedList<>() {
+ {
+ add(new Frame());
+ }
+ };
public void recordOneShot(int pins) {
+ var frame = frames.getLast();
+ frame.recordOneShot(pins);
totalScore += pins;
calcSpareAddScore(pins);
calcStrikeAddScore(pins);
beforePins = pins;
shotCount = shotCount == 1 && strikeAddScoreCount < 2 && doubleAddScoreCount < 2 ? 2 : 1;
+ if (frame.isFinished()) {
+ frames.add(new Frame());
+ }
}
public int getTotalScore() {
return totalScore;
}
+ public int getFrameScore(int frameNum) {
+ return frames.get(frameNum - 1).getScore();
+ }
// ry
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
public class FrameTest {
private Frame frame;
@BeforeEach
public void setup() {
frame = new Frame();
}
// ry
+ @Test
+ public void test_spare_when_defect_10_pins_in_second_shot_of_frame() {
+ frame.recordOneShot(5);
+ assertThat(frame.isSpare()).isFalse();
+ frame.recordOneShot(5);
+ assertThat(frame.isSpare()).isTrue();
+ }
}
Green
public class Frame {
private int score = 0;
private int shotCount = 0;
public void recordOneShot(int pins) {
score += pins;
shotCount++;
}
public int getScore() {
return score;
}
public boolean isFinished() {
return shotCount >= 2 || score >= 10;
}
+ public boolean isSpare() {
+ return score == 10 && shotCount >= 2;
+ }
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class FrameTest {
private Frame frame;
@BeforeEach
public void setup() {
frame = new Frame();
}
//ry
+ @Test
+ public void test_strike_when_defect_10_pins_in_first_shot_of_frame() {
+ assertThat(frame.isStrike()).isFalse();
+ frame.recordOneShot(10);
+ assertThat(frame.isStrike()).isTrue();
+ }
}
Green
public class Frame {
private int score = 0;
private int shotCount = 0;
public void recordOneShot(int pins) {
score += pins;
shotCount++;
}
public int getScore() {
return score;
}
public boolean isFinished() {
return shotCount >= 2 || score >= 10;
}
public boolean isSpare() {
return score == 10 && shotCount >= 2;
}
+ public boolean isStrike() {
+ return score == 10 && shotCount == 1;
+ }
}
import java.util.LinkedList;
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
private int strikeAddScoreCount = 0;
private int doubleAddScoreCount = 0;
private final LinkedList<Frame> frames = new LinkedList<>() {
{
add(new Frame());
}
};
public void recordOneShot(int pins) {
var frame = frames.getLast();
frame.recordOneShot(pins);
totalScore += pins;
calcSpareAddScore(pins);
calcStrikeAddScore(pins);
if (frame.isFinished()) {
frames.add(new Frame());
}
}
public int getTotalScore() {
return totalScore;
}
public int getFrameScore(int frameNum) {
return frames.get(frameNum - 1).getScore();
}
private void calcSpareAddScore(int pins) {
if (isSpare) {
totalScore += pins;
isSpare = false;
}
- checkSpare(pins);
+ if (frames.getLast().isSpare()) {
+ isSpare = true;
+ }
}
- private void checkSpare(int pins) {
- if (shotCount == 2 && pins + beforePins == 10) {
- isSpare = true;
- }
- }
private void calcStrikeAddScore(int pins) {
addStrikeScore(pins);
addDoubleScore(pins);
if (isStrike(pins)) {
recognizeStrikeAddCount();
}
}
private void addStrikeScore(int pins) {
if (strikeAddScoreCount > 0) {
totalScore += pins;
strikeAddScoreCount -= 1;
}
}
private void addDoubleScore(int pins) {
if (doubleAddScoreCount > 0) {
totalScore += pins;
doubleAddScoreCount -= 1;
}
}
private boolean isStrike(int pins) {
return pins == 10;
}
private void recognizeStrikeAddCount() {
if (strikeAddScoreCount == 0) {
strikeAddScoreCount = 2;
} else {
doubleAddScoreCount = 2;
}
}
}
import java.util.LinkedList;
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
private int strikeAddScoreCount = 0;
private int doubleAddScoreCount = 0;
private final LinkedList<Frame> frames = new LinkedList<>() {
{
add(new Frame());
}
};
public void recordOneShot(int pins) {
var frame = frames.getLast();
frame.recordOneShot(pins);
totalScore += pins;
calcSpareAddScore(pins);
calcStrikeAddScore(pins);
if (frame.isFinished()) {
frames.add(new Frame());
}
}
public int getTotalScore() {
return totalScore;
}
public int getFrameScore(int frameNum) {
return frames.get(frameNum - 1).getScore();
}
private void calcSpareAddScore(int pins) {
if (isSpare) {
totalScore += pins;
isSpare = false;
}
if (frames.getLast().isSpare()) {
isSpare = true;
}
}
private void calcStrikeAddScore(int pins) {
addStrikeScore(pins);
addDoubleScore(pins);
- if (isStrike(pins)) {
+ if (frames.getLast().isStrike()) {
recognizeStrikeAddCount();
}
}
private void addStrikeScore(int pins) {
if (strikeAddScoreCount > 0) {
totalScore += pins;
strikeAddScoreCount -= 1;
}
}
private void addDoubleScore(int pins) {
if (doubleAddScoreCount > 0) {
totalScore += pins;
doubleAddScoreCount -= 1;
}
}
private void recognizeStrikeAddCount() {
if (strikeAddScoreCount == 0) {
strikeAddScoreCount = 2;
} else {
doubleAddScoreCount = 2;
}
}
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class FrameTest {
private Frame frame;
@BeforeEach
public void setup() {
frame = new Frame();
}
// ry
+ @Test
+ public void test_spare_add_bonus_score() {
+ frame.recordOneShot(5);
+ frame.recordOneShot(5);
+ frame.addBonusScore(5);
+ assertThat(frame.getScore()).isEqualTo(15);
+ }
}
Green
package bowling;
public class Frame {
private int score = 0;
private int shotCount = 0;
public void recordOneShot(int pins) {
score += pins;
shotCount++;
}
public int getScore() {
return score;
}
public boolean isFinished() {
return shotCount >= 2 || score >= 10;
}
public boolean isSpare() {
return score == 10 && shotCount >= 2;
}
public boolean isStrike() {
return score == 10 && shotCount == 1;
}
+ public void addBonusScore(int bonusScore) {
+ score += bonusScore;
+ }
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class FrameTest {
private Frame frame;
@BeforeEach
public void setup() {
frame = new Frame();
}
// ry
+ @Test
+ public void test_spare_frame_score_add_next_pins() {
+ game.recordOneShot(4);
+ game.recordOneShot(6);
+ game.recordOneShot(5);
+ assertThat(game.getFrameScore(1)).isEqualTo(15);
+ assertThat(game.getTotalScore()).isEqualTo(20);
+ }
}
Green
import java.util.LinkedList;
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
+ private Frame spareFrame = null;
private int strikeAddScoreCount = 0;
private int doubleAddScoreCount = 0;
private final LinkedList<Frame> frames = new LinkedList<>() {
{
add(new Frame());
}
};
// ry
private void calcSpareAddScore(int pins) {
if (isSpare) {
totalScore += pins;
isSpare = false;
+ spareFrame.addBonusScore(pins);
+ spareFrame = null;
}
if (frames.getLast().isSpare()) {
isSpare = true;
+ spareFrame = frames.getLast();
}
}
// ry
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class FrameTest {
private Frame frame;
@BeforeEach
public void setup() {
frame = new Frame();
}
// ry
@Test
public void test_strike_frame_score_add_twice_next_pins() {
game.recordOneShot(10);
game.recordOneShot(3);
game.recordOneShot(4);
game.recordOneShot(2);
recordSamePinsManyShot(15, 0);
assertThat(game.getTotalScore()).isEqualTo(26);
assertThat(game.getFrameScore(1)).isEqualTo(17);
}
}
Green
import java.util.LinkedList;
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
private Frame spareFrame = null;
+ private Frame strikeFrame = null;
private int strikeAddScoreCount = 0;
private int doubleAddScoreCount = 0;
private final LinkedList<Frame> frames = new LinkedList<>() {
{
add(new Frame());
}
};
// ry
private void addStrikeScore(int pins) {
if (strikeAddScoreCount > 0) {
totalScore += pins;
strikeAddScoreCount -= 1;
+ strikeFrame.addBonusScore(pins);
}
}
private void addDoubleScore(int pins) {
if (doubleAddScoreCount > 0) {
totalScore += pins;
doubleAddScoreCount -= 1;
}
}
private void recognizeStrikeAddCount() {
if (strikeAddScoreCount == 0) {
strikeAddScoreCount = 2;
+ strikeFrame = frames.getLast();
} else {
doubleAddScoreCount = 2;
}
}
}
Red
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class FrameTest {
private Frame frame;
@BeforeEach
public void setup() {
frame = new Frame();
}
// ry
+ @Test
+ public void test_strike_double_frame_score() {
+ game.recordOneShot(10);
+ game.recordOneShot(10);
+ game.recordOneShot(4);
+ game.recordOneShot(2);
+ recordSamePinsManyShot(14, 0);
+ assertThat(game.getTotalScore()).isEqualTo(46);
+ assertThat(game.getFrameScore(1)).isEqualTo(24);
+ assertThat(game.getFrameScore(2)).isEqualTo(16);
+ }
}
Green
import java.util.LinkedList;
public class BowlingGame {
private int totalScore = 0;
private boolean isSpare = false;
private Frame spareFrame = null;
private Frame strikeFrame = null;
+ private Frame doubleFrame = null;
private int strikeAddScoreCount = 0;
private int doubleAddScoreCount = 0;
private final LinkedList<Frame> frames = new LinkedList<>() {
{
add(new Frame());
}
};
// ry
private void addDoubleScore(int pins) {
if (doubleAddScoreCount > 0) {
totalScore += pins;
doubleAddScoreCount -= 1;
+ doubleFrame.addBonusScore(pins);
}
}
private void recognizeStrikeAddCount() {
if (strikeAddScoreCount == 0) {
strikeAddScoreCount = 2;
strikeFrame = frames.getLast();
} else {
doubleAddScoreCount = 2;
+ doubleFrame = frames.getLast();
}
}
}
Next, move the judgment by strikeAddScoreCount and doubleAddScoreCount to Frame.
Even so far, it seems that you can imagine it to some extent, so once so far (exhausted)
I don't know for my understanding.
First of all (sorry for the rough image), please make the following image from the image.
――The outermost circle below is the entire development target, and there are several problems / issues (gray circles in the image below) in it = The development target is a collection (lump) of problems / issues --There are problems that become the core of the target as you move toward the center of the circle. ――The understanding of the target is poor, and the developer can only recognize the problems / issues that can be seen from the outside.
Based on the above, when it comes to actually proceeding with development, if the understanding of the target is poor, for this mass,
――I don't know what to start with ――I don't know how to proceed
I think it will be. On the other hand, TDD Solve in order from the one that can be solved one by one from the outside. Even if it was a detour at first glance. Ideally, the shortest route can solve the problems, but it is practically impossible to solve the problems with the shortest route when there is an inner part that cannot be seen yet. Also, if you think that it is the shortest and try to solve multiple problems at the same time, it will be complicated and it will take more time, and it will often happen that you make complicated work.
That is why we break it down into smaller particles and solve the problems one by one.
By unraveling one by one, you will gradually approach the core and advance your understanding of the object. By the time you reach the core or a problem close to it, your understanding of the subject will deepen and you will be able to make the right decisions about the problem (that's why you shouldn't decide until you can make the right decision. Even if you make a tentative decision in a state where you can't do it yet, it will be an extra work).
When actually implementing it, I think it is ** defining / declaring which problem to solve by writing a test case **.
Based on the above, considering the TDD approach, TDD is
** Set small granular goals (goals to aim for and problems to be solved) by writing test cases, and focus on only one goal in front of you. This is repeated. ** **
** By doing that separately, even static and dynamic design **
--Static design ≒ Work to consider only logic / algorithm (when Red → Green) --Dynamic design ≒ Work to consider only class / interface design (at the time of Refactor)
When you do one, you can focus on the other, and by going through the Red / Green / Refactor cycle, you can refine the design / implementation while keeping the design quality to some extent (depending on the developer's competence). I think.
In addition, since Refactor is based on the peace of mind and trust that can be obtained with the following as collateral, you can focus on the work of thinking about dynamic design.
--Achievements of understanding / implementing the logic necessary to achieve the goal --Absolute index that judges OK / NG called test
I understood. that's all
[Book: Getting Started with TDD Test Driven Development](https://www.amazon.co.jp/%E3%81%93%E3%82%8C%E3%81%8B%E3%82%89%E3 % 81% AF% E3% 81% 98% E3% 82% 81% E3% 82% 8BTDD-% E3% 83% 86% E3% 82% B9% E3% 83% 88% E9% A7% 86% E5% 8B% 95% E9% 96% 8B% E7% 99% BA% E5% 85% A5% E9% 96% 80-ThinkIT-Books-% E5% 90% 89% E8% B0% B7-ebook / dp / B00VFQ7WCM / ref = cm_cr_arp_d_product_top? ie = UTF8)
[Book: Test Driven Development](https://www.amazon.co.jp/%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95 % E9% 96% 8B% E7% 99% BA-% EF% BC% AB% EF% BD% 85% EF% BD% 8E% EF% BD% 94% EF% BC% A2% EF% BD% 85% EF% BD% 83% EF% BD% 8B-ebook / dp / B077D2L69C / ref = sr_1_1? __mk_ja_JP =% E3% 82% AB% E3% 82% BF% E3% 82% AB% E3% 83% 8A & dchild = 1 & keywords =% E3% 83% 86% E3% 82% B9% E3% 83% 88% E9% A7% 86% E5% 8B% 95% E9% 96% 8B% E7% 99% BA & qid = 1605338667 & s = digital-text & sr = 1-1)