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

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

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

## 全部1ピンだけ倒したときのテストを追加

BowlingGameTestに以下の機能を実装。

- ピンの本数を記録する$scoreプロパティをBowlingGameに設定
- 全投球で 1 ピンのみ倒したときの正しい挙動を保証させる

### テストを書く

```php
    /**
     * 全投球で 1 ピンのみ倒す
     */
    public function testKnockedDownAllOne()
    {
        $this->BowlingGame = new BowlingGame();

        for ($i = 0; $i < 20; $i++) {
            $this->BowlingGame->recordShot(1);
        }

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

### 実行して怒られる

だんだん怒られるのにも慣れてきたかな。。

```bash
$ vendor/bin/phpunit tests/BowlingGameTest.php 
PHPUnit 5.5.4 by Sebastian Bergmann and contributors.

.F                                                                  2 / 2 (100%)

Time: 27 ms, Memory: 4.00MB

There was 1 failure:

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

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

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

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

プロパティを追加したのでconstruct()で初期化

```php
<?php

namespace App;

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

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

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

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

### 実行する

```bash
$ vendor/bin/phpunit tests/BowlingGameTest.php 
PHPUnit 5.5.4 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 30 ms, Memory: 4.00MB

OK (2 tests, 2 assertions)
```

## リファクタリング
機能(ふるまい)を変更せずに設計を改善する

まずは重複コードを改善する


```php:BowlingGameTest.php
    /**
     * 全投球がガーター
     */
    public function testAllGarter()
    {
        $this->BowlingGame = new BowlingGame();

        for ($i = 0; $i < 20; $i++) {
            $this->BowlingGame->recordShot(0);
        }

        $this->assertTrue(0 === $this->BowlingGame->calculateScore());
    }

    /**
     * 全投球で 1 ピンのみ倒す
     */
    public function testKnockedDownAllOne()
    {
        $this->BowlingGame = new BowlingGame();

        for ($i = 0; $i < 20; $i++) {
            $this->BowlingGame->recordShot(1);
        }

        $this->assertEquals(20, $this->BowlingGame->calculateScore());
    }
```
20回の繰り返し投球処理が重複しているので
メソッドにまとめる
(インスタンス生成もsetUp()に移した)
↓

```php:BowlingGameTest.php
<?php
use PHPUnit\Framework\TestCase;
use App\BowlingGame;

class BowlingGameTest extends TestCase
{

    public $BowlingGame;

    public function setUp()
    {
        $this->BowlingGame = new BowlingGame();
    }

    public function tearDown()
    {
        unset($this->BowlingGame);
    }

    /**
     * 全投球がガーター
     */
    public function testAllGarter()
    {
        $this->loopRecordShot(20, 0);

        $this->assertTrue(0 === $this->BowlingGame->calculateScore());
    }

    /**
     * 全投球で 1 ピンのみ倒す
     */
    public function testKnockedDownAllOne()
    {
        $this->loopRecordShot(20, 1);

        $this->assertEquals(20, $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);
        }
    }
}

```

テストを動かして通っていればok!
ソースコードのリファクタリングもテストコードのリファクタリングも
テストケースを作成していれば比較的簡単に行える。
なぜなら、プログラムも自分自身も正しい前提条件と正しい結果を理解しているから。