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

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

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

## ストライクの場合のテストケース

BowlingGameTestに以下の機能を実装。

- ストライク時の点数計算が正しく動くようにする

### テストを書く

```php
    /**
     * 初球ストライクのケース
     *
     * 1投目: ストライク
     * 2投目: 3
     * 3投目: 3
     * 4投目: 1
     *
     * 残りガター
     *
     * @return [type] [description]
     */
    public function testStrikeAtFirstFlame()
    {
        $this->BowlingGame->recordShot(10);
        $this->BowlingGame->recordShot(3);
        $this->BowlingGame->recordShot(3);
        $this->BowlingGame->recordShot(1);

        $this->loopRecordShot(15, 0);

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

### 実行して怒られる

```bash
There was 1 failure:

1) BowlingGameTest::testStrikeAtFirstFlame
Failed asserting that 17 matches expected 23.

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

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

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

① ストライクの判定をし、正しい点数計算が行われるようにする

#### ボーナス回数を管理させる
(ストライクは、次のフレームの２投目までがポイントに追加)

- __strikeBonusCount__を定義
  -  __0 < strikeBonusCount__ → scoreにpinの数を追加。strikeBonusCount-1
  -  __pin = 10__ → strikeBonusCountに2をセット


```php
<?php

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

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

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

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

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

    public function __construct()
    {
        $this->score = 0;
        $this->isSpare = false;
        $this->lastPin = 0;
        $this->shotNo = 0;
        $this->strikeBonusCount = 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;
        }

        /**
         * ストライクでのボーナス計算処理
         */
        if ($this->strikeBonusCount > 0) {
            $this->score += $pin;
            --$this->strikeBonusCount;
        }

　　　　/**
        * ストライク時にボーナス管理の変数をセット
        */
        if ($pin === 10) {
            $this->strikeBonusCount = 2;
        }

        $this->lastPin = $pin;
    }

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

```

② 実装が正しいか確認

```bash
OK (5 tests, 5 assertions)
```

### リファクタリング

#### 点数計算のrecordShotが巨大メソッドになってきのでリファクタリング

テスト → 成功を繰り返して増えたコードを改善していくバランスが必要。
__テストがあるのでリファクタリングのリスクは格段に少ないのがいいところ。__


①record_shot内で切り出せそうな部分をメソッドとして切り出す

今回行ったのは下記

- スペアのロジックの切り出し
- ストライクのロジックの切り出し
- 今回の投球が次回ボーナス計算になるか判定

```php
<?php

namespace App;

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

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

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

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

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

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

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

        $this->score += $pin;

        $this->calculateSpare($pin);

        $this->calculateStrike($pin);

        $this->setBonusParam($pin);

        $this->lastPin = $pin;
    }

    /**
     * スペアの場合、もう一度ピンの数を足して
     * フラグを下げる
     * @param int $pin
     * @return void [description]
     */
    private function calculateSpare(int $pin)
    {
        if ($this->isSpare) {
            $this->score += $pin;
            $this->isSpare = false;
        }
    }

    /**
     * ストライク時の得点計算
     * @param  int    $pin [description]
     * @return void      [description]
     */
    private function calculateStrike(int $pin)
    {
        if ($this->strikeBonusCount > 0) {
            $this->score += $pin;
            --$this->strikeBonusCount;
        }
    }

    /**
     * ストライク時にボーナス管理の変数をセット
     * @param  int     $pin [description]
     * @return void      [description]
     */
    private function isStrike(int $pin)
    {
        if ($pin === 10) {
            $this->strikeBonusCount = 2;
        }
    }

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

    /**
     * 今回の投球がボーナス回として次の投球に影響するか判定
     * @param int $pin [description]
     * @return void
     */
    private function setBonusParam(int $pin)
    {
        $this->isSpare($pin);
        $this->isStrike($pin);
    }

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

```

② 確認

```bash
OK (5 tests, 5 assertions)
```

## 感想

やっぱテストがあると安心してリファクタできるのがいい。
一回目の実装でリファクタ不要なんてスーパーエンジニアはほんの一握りだろうから
凡人はおとなしくテスト書いて、コードを楽によくできる環境作るべし。
時間や、納期との兼ね合いは難しいけど、そこをわからせるのもまた仕事。。。