PHPテスト環境の構築とテスト駆動開発の練習

#　PHPテスト環境の構築

PHPのテスト環境をちゃちゃっと構築する手順を書きます。
OSはUbuntu13.10です。

##　composerの導入とインストール
次のコマンドで最新のcomposer.pharをとってきます。

```
mkdir proj
cd proj
sudo apt-get install php5 php5-json curl
curl -sS https://getcomposer.org/installer | php
```
`composer.json`を作ります。

```json
{
  "require": {
    "piece/stagehand-testrunner": "4.2.*"
  },
  "require-dev": {
      "phpunit/phpunit": "3.7.*"
  }
}
```


###### 2017/03/20
`piece/stagehand-testrunner`のバージョンを上げた。依存関係を解決してtestrunnerが動作するようにする。


ではcomposerを使ってインストールです。
ちょっと時間がかかります。

```
 php composer.phar install --dev
```
##　PHPUnitを動かしてみる

```
mkdir sample
mkdir sample/tests
vi sample/tests/SampleTest.php
```

```php
<?php
Class SampleTest extends PHPUnit_Framework_TestCase
{
    /**
     * @test
     */
    public function 試しに実行してみる()
    {
        $this->assertTrue(true);
    }
}
```
```
./vendor/bin/phpunit --tap  sample/tests/
```
次のような結果になればおｋです。

```
TAP version 13
ok 1 - SampleTest::試しに実行してみる
1..1
```

#　テスト駆動開発っぽいことを

さてインストールして動かしてみるだけでなく、ちょっとはテスト駆動開発っぽいことをやってみようと思います。

``` 
mkdir leapyear
vi leapyear/LeapYear.php
```

```php
<?php
class LeapYear
{
    public function isLeapYear($year)
    {
        return false;
    }
}
```

`LeapYear`クラスがあります。このクラスはうるう年かどうかを判定する`isLeapYear($year)`メソッドを持っています(「うるう年」クラスがうるう年かどうかを判定するメソッドを持っているなんておかしい？まぁそのあたりは大目に見てください)。今はfalseを返すだけですので、実装してみましょう。

テスト駆動開発ではテストから書き始めるわけですが、テストする項目を決めます。こんな感じになるでしょう。

* 4で割り切れる年はうるう年と判定します
* ただし、100で割り切れる年はうるう年でないと判定します
* ただし、400で割り切れる年はうるう年と判定します

テストする項目を列挙すれば、そのままメソッドの仕様も決定しますね。
ではテストコードを書き始めます。が、その前に

## testrunnerを用意する

まずは、以下のコマンドを実行します。

```
./vendor/bin/testrunner compile -p vendor/autoload.php
```

そしてtestrunnerを起動です。

```
./vendor/bin/testrunner phpunit -p vendor/autoload.php -a leapyear
```

`No tests executed!`と表示されたと思います。テストをまだ書いていませんからね。
これからは、TestRunnerが新規ファイルの作成やファイルの更新をキャッチし自動的にテストを実行してくれます。

それではテストコードを書いていきます。

```
mkdir leapyear/tests
vi leapyear/tests/LeapYearTest.php
```

```php
<?php
require_once (dirname(__FILE__) . "/../LeapYear.php");

class IsLeapYearTest extends PHPUnit_Framework_TestCase
{
    /**
     * @test
     */
    public function _4で割り切れる年はうるう年と判定する()
    {
        $this->assertTrue((new LeapYear())->isLeapYear(4));
    }
}
```

ファイルを保存するのと同時にテストが実行され、テストに失敗するのが確認できましたか？確認ができたらテストが通るように`isLeapYear($year)`を書き換えましょう。まずはこんな感じに書き換えます。

```php
<?php

class LeapYear
{
    public function isLeapYear($year)
    {
        if ($year % 4 === 0) {
                return true;
        }
        return false;
    }
}
```

ファイルを保存するとテストが成功すると思います。テストに失敗して赤文字が表示されていた部分が緑色に変わるのを見ると、なんだか楽しくなってきます。

それではテストケースを追加します。ちゃっちゃと行きましょう。

```php
<?php
require_once (dirname(__FILE__) . "/../LeapYear.php");

class IsLeapYearTest extends PHPUnit_Framework_TestCase
{
    /**
     * @test
     */
    public function _4で割り切れる年はうるう年と判定する()
    {
        $this->assertTrue((new LeapYear())->isLeapYear(4));
    }

    /**
     * @test
     */
    public function _100で割り切れる年はうるう年でないと判定する()
    {
        $this->assertFalse((new LeapYear())->isLeapYear(100));
    }
}
```

テストに失敗しましたね？では通るようにしましょう。

```php
<?php

class LeapYear
{
    public function isLeapYear($year)
    {
        if ($year % 4 === 0) {
                if ($year % 100 === 0 ) {
                        return false;
                }
                return true;
        }
        return false;
    }
}
```

こんな感じです。
それでは最後のテストケースを追加しましょう。

```php
<?php
require_once (dirname(__FILE__) . "/../LeapYear.php");

class IsLeapYearTest extends PHPUnit_Framework_TestCase
{
    /**
     * @test
     */
    public function _4で割り切れる年はうるう年と判定する()
    {
        $this->assertTrue((new LeapYear())->isLeapYear(4));
    }

    /**
     * @test
     */
    public function _100で割り切れる年はうるう年でないと判定する()
    {
        $this->assertFalse((new LeapYear())->isLeapYear(100));
    }

    /**
     * @test
     */
    public function _400で割り切れる年はうるう年と判定する ()
    {
        $this->assertTrue((new LeapYear())->isLeapYear(400));
    }
} 
```

テストが通るようにします。

```php
<?php

class LeapYear
{
    public function isLeapYear($year)
    {
        if ($year % 4 === 0) {
                if ($year % 100 === 0 ) {
                        if ($year % 400 === 0 ) {
                                return true;
                        }
                        return false;
                }
                return true;
        }
        return false;
    }
}
```
これでめでたくうるう年かどうかを正しく判定できるようになりました。
ですが、まだこれで終わりではありません。

## テスト駆動開発の基本サイクル

説明が最後になりましたが、テスト駆動開発はRED、GREEN、REFACTORという3つの作業サイクルをぐるぐると回しながら進めていきます。

1. RED:失敗するテストを書きます。
2. GREEN:テストを成功させる最低限のコードを書きます。
3. REFACTOR:コードをリファクタリングしてきれいにします。

このサイクルを、場合によっては数十秒という超短期で回しながら進むのがテスト駆動開発のやり方になります。
今回はテストが通ったらテストケースを追加してそのテストを通るようにする、というやり方で進めましたが、このくらいの規模ならまあいいかなと思ってこうしました。ですが、if文のネストが深くなってきたことですし、そろそろコードを綺麗にしたほうがいいと思います。

**大事なこと** 
わざわざ太字にして強調するようなことでもないですが、テストが成功する限りは同条件での挙動は同一です。つまり思い切ったコード変更をしても「大丈夫かなあ」とびくびくする必要がなくなります。間違いがあったらPHPUnitが教えてくれます。

というわけではあとはコードを綺麗に書き換えてみてはいかがでしょうか。きっと楽しいと思います。