最近、テストコードを書いているのですが、DB 周りのテストに大変苦戦しておりました。PHPUnit を使って書いているのですが、ただの Insert 文をテストするのにもかなり遠回りしなくちゃいけないことがあるんですよね。。。
今回は、そんな PHPUnit の拡張機能である DBUnit を使ってみます。
参考サイト:
https://phpunit-tenkoma-working.readthedocs.io/ja/latest/database/
https://www.ritolab.com/entry/168
https://qiita.com/takatama/items/63c7c82108af48b7bbdb
何ができるか
以下が DBUnit の特徴です。
- DB の初期状態を別ファイルで定義して、テスト用の初期環境を作る。
- 上記の別ファイルデータを、データセットやデータテーブルと呼ぶ。
- アサーションで比較に使う値もデータセットで定義可能。
- データセットは、XML、YAML、CSV、PHP の配列等々をサポート。
- テーブルの行数カウントや、テーブルの情報自体を比較可能。
- テスト実行後は、(特定のメソッドを定義すれば)自動で DB の変更を消去!
偉い感じしますね!
インストール方法
今回は、Composer を使ってインストールします。
アプリケーションのディレクトリで、下記コマンドを実行します。
composer require --dev phpunit/dbunit
同ディレクトリの、composer.json に下記記述が入っているはずです。
"require-dev": {
"phpunit/dbunit": "^4.0"
}
これで DBUnit のインストールは完了です。
DBUnit 使い方
phpunit.xml に下記の記述をします。
ここでは、テスト用 DB に接続するための定数を定義しています。
<phpunit bootstrap="..\vendor\autoload.php">
<php>
<ini name="display_errors" value="on"/>
<var name="DB_DSN" value="mysql:dbname=local_test;host=localhost" />
<var name="DB_USER" value="root" />
<var name="DB_PASSWD" value="" />
<var name="DB_DBNAME" value="local_test" />
</php>
</phpunit>
次に、DB 接続するメソッドを記述した、抽象クラスを定義します。
各テストコードは、このクラスを継承することになります。
<?php
use PHPUnit\Framework\TestCase;
use PHPUnit\DbUnit\TestCaseTrait;
abstract class Generic_Tests_DatabaseTestCase extends TestCase{
use TestCaseTrait;
// PDO のインスタンス生成は、クリーンアップおよびフィクスチャ読み込みのときに一度だけ
static protected $pdo = null;
// PHPUnit\Dbunit\Database\Connection のインスタンス生成は、テストごとに一度だけ
private $connection = null;
// DB接続
final public function getConnection() {
// TODO: Implement getConnection() method.
if ($this->connection === null) {
if (self::$pdo == null) {
self::$pdo= new PDO($GLOBALS['DB_DSN'], $GLOBALS['DB_USER'], $GLOBALS['DB_PASSWD']);
}
$this->connection = $this->createDefaultDBConnection(self::$pdo, $GLOBALS['DB_DBNAME']);
}
return $this->connection;
}
// DB のクリーンアップ
public function getTearDownOperation() {
return \PHPUnit\DbUnit\Operation\Factory::TRUNCATE();
}
}
フィクスチャ用のデータセットを作ります。
今回は YAML ファイルを使います。
food:
-
id: 1
name: karaage
price: 450
それでは、テストコードを書きます。
<?php
require_once('Generic_Tests_DatabaseTestCase.php');
require_once('../../class/food.class.php');
use PHPUnit\DbUnit\DataSet\YamlDataSet;
class TestSample extends Generic_Tests_DatabaseTestCase {
/**
* フィクスチャ設定
* @return \PHPUnit\DbUnit\DataSet\IDataSet|YamlDataSet
*/
protected function getDataSet() {
// TODO: Implement getDataSet() method.
return new YamlDataSet(dirname(__FILE__).'\fixture_sample\sample.yml');
}
/**
* テストコード
*/
public function testGetDriverID() {
// 初期状態の行数アサーション
$this->assertEquals(1, $this->getConnection()->getRowCount('food'));
// YAML ファイルで定義してあるデータから、アサーション用の比較データ取得。
// 本来はフィクスチャ用のファイルと別に作るが、今回は Insert, Update, Delete を行わないため同じものを使用。
$expectedTable = $this->getDataSet()->getTable('food')->getRow(0);
// テストしたいメソッドの実行。
$actual = Food::getById($expectedTable['id'], self::$pdo);
// アサーション用のキー配列。created はテストしない。
$check_keys_array = [
'id', 'name', 'price'
];
// 値が一致しているかのアサーション
foreach ($check_keys_array as $key) {
$this->assertEquals($expectedTable[$key], $actual[$key]);
}
}
}
このテストの実行はコマンドライン上で下記コマンドを打ってください。
phpunit -c phpunit.xml foodTest.php
以上でテスト完了です。
テストでテーブルに変更があっても変更したところを消去してくれるんで、かなーり便利だと感じました。
一応、注意しなければならないのが、PHPUnit としては DBUnit の開発を既に終えているそうです。
ですが、foak されたプロジェクト( https://github.com/kornrunner/dbunit )があるので、検討する価値はあるかなという感じです。