PHP DBまわりのテスト

DBを使ったテストを考えた場合、
・Insertのテストが走るとTableにそのデータがInsertされテストが正常完了する
・上記テストをもう一度走らせると、前回のInsertされたデータがあるのでDuplicateでテストが失敗する

上記の問題を解決することを考える
・テストが走るとテーブルが初期化される
・Insertのテストが走ると初期化されているので、前のテストでInsertされたデータは消えているので、Duplicateにはならない。

##phpunit/dbunit
・テストが接続するDB情報を設定できる
・テストが走るとテーブルのデータが初期化される

###インストール
Composerを使ってインストール

```json:composer.json
{
    "require": {
        "phpunit/phpunit": ">=4.0,<5.0",
        "phpunit/dbunit": ">=1.3,<1.4"
    }
}
```

・テストでは本番DBにはInsertさせないのでレプリカのDBとテーブルを用意する
・DBUnitはデータベースを作ったりテーブルを作ったりは出来ないので事前に用意しておく

```sql:sql
create database hogeDB
```

・接続するユーザーを作る

```sql:sql
GRANT ALL PRIVILEGES ON hogeDB.* TO hoge@'%' IDENTIFIED BY 'password' WITH GRANT OPTION;
```

###テストが接続するためのDB情報
```xml:tests/phpunit.xml
<?xml version="1.0" encoding="UTF-8" ?>
  <phpunit>
    <php>
      <var name="DB_DSN" value="mysql:dbname=hogeDB;host=localhost" />
      <var name="DB_USER" value="hoge" />
      <var name="DB_PASSWORD" value="password" />
      <var name="DB_DBNAME" value="hogeDB" />
  </php>
</phpunit>
```

・テーブルの初期値データをmysqldumpで取得する

```
$ mkdir -p tests/fixture
$ mysqladmin --xml -t -u hoge -p hogeDB > tests/fixture/expect.xml
```

・テストの初期処理

```php:CommonDatabaseTest.php
<?php
class CommonDatabaseTest extends PHPUnit_Extensions_Database_TestCase
{
    static public $pdo = null;
    public $connection = null;

    // テストのDB接続情報
    public function getConnection()
    {
        if ( $this->connection == null ) {
            if ( self::$pdo == null ) {
                self::$pdo = new PDO($GLOBALS['DB_DSN'],
                                     $GLOBALS['DB_USER'],
                                     $GLOBALS['DB_PASSWORD']);
                self::$pdo->query('SET NAMES UTF8');
            }
            $this->connection = $this->createDefaultDBConnection(self::$pdo, $GLOBALS['DB_DBNAME']);
        }

        return $this->connection;
    }

    // テストがDBに接続してデータを初期化する処理
    public function getDataSet()
    {
        $compositeDs = new PHPUnit_Extensions_Database_DataSet_CompositeDataSet([]);

        // fixture配下の.xmlファイルをすべて読み込んでデータセットしてる
        $dir = dirname(__FILE__) . '/fixture';
        $fh  = opendir($dir);

        while ($file = readdir($fh)) {
            if ( preg_match('/^\./', $file) ) {
                continue;
            }
            if ( preg_match('/\.xml$/', $file) ) {
                $ds = $this->createMySQLXMLDataSet("$dir/$file");
                $compositeDs->addDataSet($ds);
            }
        }

        return $compositeDs;
    }
}
```

・テストファイル

```php:dbTestSample.php
require_once "CommonDatabaseTest.php";

class dbTestSample extends \CommonDatabaseTest {

    public function testRegisterUser()
    {
        // アプリ側のInsert処理
        $register = new Register();
        $result   = $register->insertMemberData([
            'name'        => '山田太郎',
            'birthday'    => '1980/08/01',
            'sex'         => 0,
        ]);

        // Insert後の期待値Tableデータ
        $expectedTables = $this->createMySQLXMLDataSet('/path/test/except/table.xml');
        $expectedMember = $expectedTables->getTable('member');

        // Insert後のTableデータ
        $doneUser = $this->getConnection()->createQueryTable('member', 'SELECT * FROM member');

        // tableの状態が同じか
        $this->assertTablesEqual($expectedMember, $doneUser);
    }

}
```

・実行

```
$ vendor/bin/phpunit -c tests/phpunit.xml tests/dbTestSample.php
```

###所感
・まだよくわかっていない。
・アプリ側のDB接続情報も、テストで動かされた場合はテストDBに接続するように変更が必要なのかな？
・SQLのテストに使える
・機能テストに使える
・データベース接続を伴うクラス、メソッドは他との依存関係をなくすように設計する