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

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

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

## Flameクラスの組み込み

### Flameがスペアの判定をするようにする

__テスト__

```php:FlameTest.php
    /**
     * @test
     */
    public function _2投目で10ピン倒した場合はスペア()
    {
        $this->Flame->recordShot(5);
        $this->assertFalse($this->Flame->isSpare()); //1投目5ピンなのでスペアでない
        $this->Flame->recordShot(5);
        $this->assertTrue($this->Flame->isSpare()); //2投目合計10ピンなのでスペア
    }
```

__テスト実行__

```bash
1) FlameTest::_2投目で10ピン倒した場合はスペア
Error: Call to undefined method App\Flame::isSpare()

/var/www/html/test/testDrivenBowling/tests/FlameTest.php:68

ERRORS!
Tests: 16, Assertions: 25, Errors: 1.

```

__ロジック修正__

```php:Flame.php
    /**
     * @return bool
     */
    public function isSpare(): bool
    {
        if ($this->score === 10 && $this->shotCount > 1) {
            return true;
        }

        return false;
    }
```

__テスト実行__

```bash
OK (16 tests, 27 assertions)
```

### ストライクの判定ができるようにする

__テスト__

```php:FlameTest.php
    /**
     * @test
     */
    public function _1投目で10ピン倒した場合はストライク()
    {
        $this->Flame->isStrike(); //投球前はストライクではない
        $this->Flame->recordShot(10);
        $this->assertTrue($this->Flame->isStrike()); //1投目で10ピン倒したのでストライク
    }
```

__テスト実行__

```bash
1) FlameTest::_1投目で10ピン倒した場合はストライク
Error: Call to undefined method App\Flame::isStrike()

/var/www/html/test/testDrivenBowling/tests/FlameTest.php:78

ERRORS!
Tests: 17, Assertions: 27, Errors: 1.

```

__ロジック修正__

```php:Flame.php
    /**
     * @return bool
     */
    public function isStrike(): bool
    {
        if ($this->score === 10 && $this->shotCount === 1) {
            return true;
        }

        return false;
    }
```

__テスト実行__

```bash
OK (17 tests, 28 assertions)
```

### BowlingGame側のスペア判定をFlameに行わせる


__ロジック修正__

```php:BowlingGame.php
    /**
     * 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;
        }
    }
```

↓

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

```

__テスト実行__

```bash
OK (11 tests, 20 assertions)
```

### BowlingGame側のストライク判定をFlameに行わせる


__ロジック修正__

```php:BowlingGame.php
    /**
     * ストライク時にボーナス管理の変数をセット
     * @param  int     $pin [description]
     * @return void      [description]
     */
    private function isStrike(int $pin)
    {
        if ($pin === 10) {

            if ($this->strikeBonusCount !== 0) {
                $this->doubleBonusCount = 2;
            }

            if ($this->strikeBonusCount === 0) {
                $this->strikeBonusCount = 2;
            }
        }
    }
```

↓

```php
    /**
     * ストライク時にボーナス管理の変数をセット
     * @return void      [description]
     */
    private function isStrike()
    {
        if ($this->Flames[count($this->Flames)-1]->isStrike()) {

            if ($this->strikeBonusCount !== 0) {
                $this->doubleBonusCount = 2;
            }

            if ($this->strikeBonusCount === 0) {
                $this->strikeBonusCount = 2;
            }
        }
    }
```

__テスト実行__

```bash
OK (17 tests, 28 assertions)
```

### BowlingGameから不要となったプロパティ$shotNo, $lastPinを削除

リファクタリング後

```php:BowlingGame.php
<?php

namespace App;

use App¥Flame;

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

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

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

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

    /** @var array */
    private $Flames;

    public function __construct()
    {
        $this->score = 0;
        $this->isSpare = false;
        $this->strikeBonusCount = 0;
        $this->doubleBonusCount = 0;
        $this->Flames[] = new Flame(); // まずはフレーム一つだけでインスタンス作成
    }

    /**
     * ボーリングの1投球の計算を実行させる
     *
     * @param int    $pin [description]
     * @return void
     */
    public function recordShot(int $pin)
    {
        //配列の一番最後が現在のフレーム
        $this->Flames[count($this->Flames)-1]->recordShot($pin);

        $this->score += $pin;

        $this->calculateSpare($pin);

        $this->calculateStrikeBonus($pin);

        $this->calculateDoubleBonus($pin);

        $this->setBonusParam();

        //フレームが完了したら新しいフレームを配列に追加
        if ($this->Flames[count($this->Flames)-1]->isFinished()) {
            $this->Flames[] = new Flame();
        }
    }

    /**
     * スペアの場合、もう一度ピンの数を足して
     * フラグを下げる
     * @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 calculateStrikeBonus(int $pin)
    {
        if ($this->strikeBonusCount > 0) {
            $this->score += $pin;
            --$this->strikeBonusCount;
        }
    }

    /**
     * ダブル時の得点計算
     * @param  int    $pin [description]
     * @return void      [description]
     */
    private function calculateDoubleBonus(int $pin)
    {
        if ($this->doubleBonusCount > 0) {
            $this->score += $pin;
            --$this->doubleBonusCount;
        }
    }

    /**
     * ストライク時にボーナス管理の変数をセット
     * @return void      [description]
     */
    private function isStrike()
    {
        if ($this->Flames[count($this->Flames)-1]->isStrike()) {

            if ($this->strikeBonusCount !== 0) {
                $this->doubleBonusCount = 2;
            }

            if ($this->strikeBonusCount === 0) {
                $this->strikeBonusCount = 2;
            }
        }
    }

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

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

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

    /**
     * @param int
     * @return int
     */
    public function flameScore(int $flameNo): int
    {
        return $this->Flames[$flameNo]->getScore($flameNo);
    }

}
```

プロパティが減って、1クラスで管理するものの範囲が狭まった。
また、isSpare()など、各所で\$pinを引数として取っていた
関数に$pinが不必要になった。