LoginSignup
2
2

More than 5 years have passed since last update.

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

Posted at

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

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

フレーム単位でのスコアを取得する

BowlingGameTestに以下の機能を実装。

  • フレーム単位でのスコアを取得できるflameScoreメソッドを実装

テストを書く

フレーム毎のスコアはBowlingGameクラスのflameScore(\$flameNumber)で
取得すると仮定($flameNumberで取得したいフレームの番号を指定)

    /**
     * 全球ガーター時の1フレーム目の得点
     */
    public function testCalculateFrameScoreAtAllGarter()
    {
        $this->loopRecordShot(20, 0);

        $this->assertEquals(0, $this->BowlingGame->flameScore(1));
    }

実行して怒られる

1) BowlingGameTest::testCalculateFrameScoreAtAllGarter
Error: Call to undefined method App\BowlingGame::flameScore()

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

ERRORS!
Tests: 10, Assertions: 9, Errors: 1.

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

    /**
     * @return int
     */
    public function flameScore(): int
    {
        return 0;
    }

テスト実行

OK (10 tests, 10 assertions)

このままでは、最初のテストケースから実装と同じことを繰り返す・・?

設計の見直し

BowlingGameから、フレーム毎に得点を取得するためのFlameという概念を抽出

BowlingGame ⇔ Flame

機能面の変更

  • BowlingGameのcalculateScoreはFlameのscoreを合算して返す
  • BowlingGameのrecordShotは現在のFlameのrecordShotを呼び出す
  • BowlingGameのflameScoreは指定されたFlameのscoreを返す

Flameクラスで全ての投球がガーターの時のテストを通す

①テストを書く

class FlameTest extends TestCase
{
    /** @var Flame */
    public $Flame;

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

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

    /**
     * @test
     */
    public function 全ての投球がガーター()
    {
        $this->Flame->recordShot(0);
        $this->Flame->recordShot(0);

        $this->assertEquals(0, $this->Flame->getScore());
    }
}

日本語でテスト名書くようにしてみた

②テスト実行

1) FlameTest::全ての投球がガーター
Error: Call to undefined method App\Flame::recordShot()

/var/www/html/test/UnitTest/tests/FlameTest.php:25

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

③テストを通すように処理を書く

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

    /**
     * @param int $pin
     */
    public function recordShot(int $pin)
    {

    }

    /**
     * @return int
     */
    public function getScore()
    {
        return 0;
    }

}

④テスト実行

OK (1 test, 1 assertion)

Flameクラスで全ての投球が1ピンだったときのテストを通す

① テストを書く

    /**
     * @test
     */
    public function 全ての投球で1ピン倒した()
    {
        $this->Flame->recordShot(1);
        $this->Flame->recordShot(1);

        $this->assertEquals(2, $this->Flame->getScore());
    }

②テスト実行

1) FlameTest::全ての投球で1ピン倒した
Failed asserting that 0 matches expected 2.

/var/www/html/test/UnitTest/tests/FlameTest.php:39

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

③テストが通るように処理を書く

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

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

    /**
     * @return int
     */
    public function getScore()
    {
        return $this->score;
    }

}

④テスト実行

OK (2 tests, 2 assertions)

感想

設計を変えて新しい概念が出てきたが、テスト駆動の基本は変わらないので
今までと同様に小さく積み上げていけば対応はできそう。
細かくリファクタリングを癖にできるようにしたい。

2
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2