[JAVA] Hold down the practical and essential images to tackle TDD

Introduction

I'll write this article to work on TDD and to share with others an essential image of TDD.

What is 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

What is incremental design?

――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

flow

--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.

Points to do

--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)

Practical image sample

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

First of all, the easy and easy way to go

All garter cases

start line

The first test to add is to write a "do nothing test"

Red

Untitled 2.png

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 Untitled 3.png

public class BowlingGame {

}

Add the smallest method

Red

Make it a test case for all pitching garters

Untitled 4.png

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

Untitled 5.png

public class BowlingGame {

+ public void recordOneShot(int pins) {
+ }

+ public int score() {
+   return 0;
+ }
}

Case where only one pin is knocked down

Red Untitled 7.png

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

Untitled 8.png

public class BowlingGame {

  private int totalScore = 0;

+ public void recordOneShot(int pins) {
+   totalScore += pins;
+ }

+ public int getTotalScore() {
+   return totalScore;
+ }
}

Refactor if it smells ominous

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);
+   }
+ }
}

A little complicated case: Spare (the number of pins for the next round will be added)

Red

Untitled 9.png

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);
    }
  }
}

Way of thinking when making corrections

--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;
  }
}

Untitled 10.png

--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.

Untitled 11.png

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;
  }
}

Let's test the blind spot of the current implementation

Red

Untitled 12.png

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

Untitled 13.png

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;
  }
}

5. Complex test cases: strike / continuous strike

strike

Red Untitled 14.png

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 Untitled 15.png

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;
  }
}

Regular refactoring

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;
+   }
+ }
}

2 consecutive strikes

Red Untitled 17.png

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 Untitled 18.png


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;
+     }
    }
  }
}

Added test cases for 3 consecutive strikes

Green Untitled 19.png


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);
    }
  }
}

Complex Case (continued): Combined Strike and Spare

1 strike & 1 spare

Red Untitled 20.png

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 Untitled 21.png

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;
      }
    }
  }
}

2 consecutive strikes + spare

Red Untitled 22.png

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 Untitled 23.png

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;
      }
    }
  }
}

Refactor (method extraction)

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;
+   }
+ }
}

Get points for each frame

Red Untitled 24.png

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 Untitled 25.png

public BowlingGame {

  // ry

+ public int getFrameScore(int frameNum) {
+   return 0;
+ }
}

If you feel a stalemate, review the static design

――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;
  }
}

When only 1 pin of all pitches is defeated

Red

Untitled 27.png

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 Untitled 28.png

public class Frame {

  private int score = 0;
  
  public void recordOneShot(int pins) {
+   score += pins;
  }

  public int getScore(int frameNum) {
-   return 0;
+   return score;
  }
}

Frame built into Bowling Game

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);
    }
  }
}

Untitled 29.png

Frame built into Bowling Game 2 (separation of responsibilities)

--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

Frame completion judgment

Red

Untitled 30.png

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 Untitled 31.png

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;
+ }
}

Frame completion judgment (strike)

Red Untitled 32.png

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 Untitled 33.png

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;
  }
}

Built-in frame judgment in Bowling Game

2 points for all frames with 1 pin for all pitches

Red Untitled 34.png

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 Untitled 35.png

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

}

Pitching result judgment

spare

Red Untitled 36.png

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 Untitled 37.png

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;
+ }

}
strike

Red Untitled 38.png

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 Untitled 39.png

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;
+ }
}

Incorporate pitching judgment into Bowling Game

spare

Untitled 40.png

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;
    }
  }
}
strike

Untitled 41.png

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;
    }
  }
}

(Bonus) score addition judgment / record after strike / spare

Move to Frame

Red Untitled 42.png

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 Untitled 43.png

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;
+ }
}

Incorporate bonus score addition judgment / record into Bowling Game

spare

Red Untitled 44.png

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 Untitled 45.png

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
}
strike

Red Untitled 46.png

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 Untitled 47.png

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;
    }
  }
}
Strike (2 consecutive)

Red Untitled 48.png

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 Untitled 49.png

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();
    }
  }
}

Continued but pending

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)

Essential image of TDD

I don't know for my understanding.

Abstract image

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.

image.png

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 **.

Image of TDD approach

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

reference

[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)

Recommended Posts

Hold down the practical and essential images to tackle TDD
Android app to select and display images from the gallery
How to narrow down the image format from the gallery on Android and then select and import multiple images
How to find the tens and ones