モニプラFacebook担当の伊藤です。
以前、PHPUnitのDBUnit拡張を使ってみるというエントリを書きましたが、その中で、
実際に使用する場合は、PHPUnit_Extensions_Database_TestCaseをさらに拡張して、データのロールバックの仕組みや、各テストケース事に異なる初期データをぶち込む仕組みを入れるなどして使い易くする必要があるかと思います。
その辺についても書こうかと思いましたが、今回はここまでということで。。
と書いておきながら、かれこれ7ヶ月も放置してしまいました。というワケで今回はこのテーマについてです。
早速ですが、上記の
・データのロールバックの仕組み
・各テストケース事に異なる初期データをぶち込む仕組み
を実現するクラスを書いてみたのでコードをさらします。
※実際に動作する事は確認済みですが、まだまだ粗いと思いますのでその辺は気にしないで下さい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
<?php class MyDatabaseTestCase extends PHPUnit_Extensions_Database_TestCase { static private $pdo = null; private $connection = null; private $dataSet = null; private $backupDataSet = null; /** * DBUnit初期化処理 * @param PHPUnit_Extensions_Database_DataSet_IDataSet $dataSet 事前に必要とするデータ * @param array $backupTableNames バックアップするテーブルの配列 */ public function initDB(PHPUnit_Extensions_Database_DataSet_IDataSet $dataSet = null, Array $backupTableNames = null) { $this->dataSet = $dataSet; // バックアップ if($backupTableNames) { $this->backupDataSet = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection()); foreach ($backupTableNames as $tableName) { $this->backupDataSet->addTable($tableName); $this->backupDataSet->getTableMetaData($tableName); } } parent::setUp(); } /** DBへの接続情報を保持したDatabaseConnectionを返す * @return PHPUnit_Extensions_Database_DB_DefaultDatabaseConnection */ public function getConnection() { 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への接続情報を保持したDatabaseConnection内のPDOオブジェクトを返す * @return PDO */ public function getPdoConnection() { return $this->connection->getConnection(); } public function getDataSet() { return $this->dataSet ? $this->dataSet : new PHPUnit_Extensions_Database_DataSet_DefaultDataSet(); } public function getRestoreDataSet() { return $this->backupDataSet ? $this->backupDataSet : new PHPUnit_Extensions_Database_DataSet_DefaultDataSet(); } public function getTearDownOperation() { return PHPUnit_Extensions_Database_Operation_Factory::CLEAN_INSERT(); } public function tearDown() { $this->getDatabaseTester()->setTearDownOperation($this->getTearDownOperation()); $this->getDatabaseTester()->setDataSet($this->getRestoreDataSet()); $this->getDatabaseTester()->onTearDown(); } public function getDataFilePath($fileName) { return dirname(__FILE__) . '/../data/' . $fileName; } } |
では、各functionについて軽〜く説明です。
1. initDB()
初期化処理。
第1引数にテストの前提条件となるdataSetを指定。
第2引数にバックアップ&リストアしたいテーブル名を指定。
動きとしては、次のようになります。
①テスト実行前に、第2引数に指定したテーブルがバックアップされる
②dataSetに指定したレコードがINSERTされる
③テスト終了後に、バックアップしたテーブルが元に戻される
2. getConnection()
必ずOverrideが必要。DB接続情報を保持したDatabaseConnectionを返す。
接続情報は別ファイルに定義してある。
1 2 3 4 5 6 7 8 9 |
<?xml version="1.0" encoding="UTF-8" ?> <phpunit> <php> <var name="DB_DSN" value="mysql:host=localhost; dbname=test_db" /> <var name="DB_USER" value="****" /> <var name="DB_PASSWD" value="*****" /> <var name="DB_DBNAME" value="test_db" /> </php> </phpunit> |
3. getPdoConnection()
PDOを直接操作したかったので定義。
4. getDataSet()
必ずOverrideが必要。
5. getRestoreDataSet()
リストアするdataSetを返す。
6. getTearDownOperation()
親functionをOverride。デフォルトではtearDown時はNONE(何もしない)が指定されているので、
リストアする為にCLEAN_INSERTを指定。
7. tearDown()
親functionをOverride。リストアする為。
8. getDataFilePath()
関係なし。
実際の使い方です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<?php class TestTargetTest extends MyDatabaseTestCase { /** @test */ public function 初期データ用意してテスト実行してDBがテスト実行前の状態に戻るテスト() { // ①初期データとバックアップしたいテーブルを指定して初期化 $this->initDB($this->createXMLDataSet($this->getDataFilePath('init_sample.xml')), array('sample' ,'sample_user', 'sample_item')); // ②ちょこっと変えたい場合などにSQLを実行できる $this->getPdoConnection()->exec("UPDATE sample SET column_1 = 3 WHERE id = 1"); // ①②で初期化されたデータを前提としてテスト対象メソッドを実行 // このメソッドはsample_userテーブルとsample_itemテーブルに変更が入るメソッド $target = new TestTarget(); $target->testTargetFunction(); // テスト対象メソッド終了後のDB状態を検証 // // 期待値用のdataSetを作成 $expectDataSet = $this->createXMLDataSet($this->getDataFilePath('expect_sample.xml')); // 結果用のdataSetを作成 $actualDataset = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection()); // dataSetにテーブル追加 $actualDataset->addTable('sample_user', 'SELECT id, column_1, column_2, column_3 FROM sample_user'); $actualDataset->addTable('sample_item', 'SELECT id, column_1, column_2, column_3 FROM sample_item'); // 期待値用と結果用のdataSetが一致しているか $this->assertDataSetsEqual($expectDataSet, $actualDataset); } /** @test */ public function テストごとに初期データやバックアップテーブルを変えても良いよ() { $this->initDB($this->createXMLDataSet($this->getDataFilePath('init_other.xml')), array('other')); ・ ・ ・ } /** @test */ public function 初期データを指定しなくても良いよ() { $this->initDB(null, array('other')); ・ ・ ・ } } |
テストが終了した後に自動的にDBがリストアされます。
(※テスト中にシンタックスエラーなど変な落ち方をした場合はリストアされません。ご注意を。)
冒頭でも言いましたが、これ、確かにちゃんと動作するのですが、
大量レコードがあるテーブルのバックアップは無理です。(検証はしてませんが)
なので、開発用DBとは別に、必要最低限のマスタなどをぶっ込んだDBUnit用のDBを用意して使用する想定です。
最後になりますが、
これってわざわざ自分で作らなくても、もっと高機能で便利なライブラリってあるんじゃないの?
と思わない日はありません。
元Javaプログラマ。現在はScala/PlayでWeb開発と、SwiftでiOSアプリ開発をしています。 Unitテストとか書いてる時が一番楽しかったりします。