[ PHPUnit ] ボーリングから考えるテスト駆動開発 4テスト目

これからはじめるTDD テスト駆動開発入門 ThinkIT Books より

# ボーリングの点数計算プログラムを作る

## スペアをとったときのテストケース

BowlingGameTestに以下の機能を実装。

- あるシナリオでスペアのスコアを計算できるようにする
  - 1投目: 3
  - 2投目: 7
  - 3投目: 4
  - 残り17投: すべてガター

計18点が計算できていればいい。

- ストライクは考慮せずにスペアの計算ができるか

### テストを書く

```php
    /**
     * スペアの場合
     *
     * 1投目: 3
     * 2投目: 7
     * 3投目: 4
     * 残り17投: すべてガター
     */
    public function testSpareCase()
    {
        $this->BowlingGame->recordShot(3);
        $this->BowlingGame->recordShot(7);
        /** ↑ スペア **/

        $this->BowlingGame->recordShot(4);

        $this->loopRecordShot(17, 0);

        $this->assertEquals(18, $this->BowlingGame->calculateScore());
    }

　　 /**
　　  * @param  int    $loop [description]
　　  * @param  int    $pin  [description]
　　  * @return void       [description]
　　  */
    private function loopRecordShot(int $loop, int $pin)
    {
        for ($i = 0; $i < $loop; $i++) {
            $this->BowlingGame->recordShot($pin);
        }
    }
```

### 実行して怒られる

スペア時のロジックがないため失敗。。

```bash
1) BowlingGameTest::testSpareCase
Failed asserting that 14 matches expected 18.

/var/www/html/test/UnitTest/tests/BowlingGameTest.php:61

FAILURES!
```

### テストが通るように処理を書く(__最小の構成で__)

① スペアの状態か判断するロジックを作る(フラグを追加)

```php
class BowlingGame
{
    /** @var int **/
    private $score;

    /** @var bool **/
    private $isSpare;

    public function __construct()
    {
        $this->score = 0;
        $this->isSpare = false;
    }

    /**
     * @param  int    $pin [description]
     * @return [type]      [description]
     */
    public function recordShot(int $pin)
    {
        $this->score += $pin;

        /**
         * スペアの場合、もう一度ピンの数を足して
         * フラグを下げる
         */
        if ($this->isSpare) {
            $this->score += $pin;
            $this->isSpare = false;
        }

        /**
         * 10点とったらフラグを立てる
         */
        if ($this->score === 10) {
            $this->isSpare = true;
        }

    }

    /**
     * @return int [description]
     */
    public function calculateScore()
    {
        return $this->score;
    }
}

```

② 別テストで失敗
すべて1ピン倒したときも、10ピンの時点でスペアフラグが立ってしまう

```bash
There was 1 failure:

1) BowlingGameTest::testKnockedDownAllOne
Failed asserting that 21 matches expected 20.

/var/www/html/test/UnitTest/tests/BowlingGameTest.php:37

FAILURES!
Tests: 3, Assertions: 3, Failures: 1.
```

③ 最小構成で通る処理を書く
前回倒したピンと今回倒したピンの合計が10ならばスペアフラグを立てる

```php
    /** @var int **/
    private $lastPin;

    /**
     * @param  int    $pin [description]
     * @return [type]      [description]
     */
    public function recordShot(int $pin)
    {
        $this->score += $pin;

        /**
         * スペアの場合、もう一度ピンの数を足して
         * フラグを下げる
         */
        if ($this->isSpare) {
            $this->score += $pin;
            $this->isSpare = false;
        }

        /**
         * 10点とったらフラグを立てる
         */
        if ($pin + $this->lastPin === 10) {
            $this->isSpare = true;
        }

        $this->lastPin = $pin;
    }

```

④スペア判断のロジックを厳密にしていく

```php
    /**
     * 直前の投球との合計が10だった場合に
     * スペアではないと判断しているか
     *
     * 1投目: 2
     * 2投目: 5
     * 3投目: 5 スペア?
     * 4投目: 1
     *
     * @return [type] [description]
     */
    public function testSpareCaseAtDifferentFlame()
    {
        $this->BowlingGame->recordShot(2);
        $this->BowlingGame->recordShot(5);
        $this->BowlingGame->recordShot(5);
        $this->BowlingGame->recordShot(8);

        $this->loopRecordShot(16, 0);

        $this->assertEquals(20, $this->BowlingGame->calculateScore());
    }
```

⑤通るように処理を実装
テストが通るように最小限の構成なので、2投目しかスペア判定をしていない

```php
<?php

namespace App;

class BowlingGame
{
    /** @var int **/
    private $score;

    /** @var int **/
    private $lastPin;

    /** @var bool **/
    private $isSpare;

    /** @var int **/
    private $shotNo;

    public function __construct()
    {
        $this->score = 0;
        $this->isSpare = false;
        $this->lastPin = 0;
    }

    /**
     * @param  int    $pin [description]
     * @return [type]      [description]
     */
    public function recordShot(int $pin)
    {
        ++$this->shotNo;

        $this->score += $pin;

        /**
         * スペアの場合、もう一度ピンの数を足して
         * フラグを下げる
         */
        if ($this->isSpare) {
            $this->score += $pin;
            $this->isSpare = false;
        }

        /**
         * 2投目のときにスペアかどうか判定
         * 10点とったらフラグを立てる
         */
        if ($this->shotNo === 2 && $pin + $this->lastPin === 10) {
            $this->isSpare = true;
        }

        $this->lastPin = $pin;
    }

    /**
     * @return int [description]
     */
    public function calculateScore()
    {
        return $this->score;
    }
}

```


## 感想

ある程度書ける人にとって、この最小限の実装で進んでいくのは
まどろっこしいのかなぁ。。
特にスペアの判定とか2投目しかしてないし。
ただ、ここで考えすぎると実装が進まない。
考え抜いて先まで実装できたとして、そのロジックが間違っていたら出戻りのロスが大きい。
それを小さく進むことで回避できるのはいいのかもしれない。

次回はストライクの実装