任意の言語の単体テストの初級および中級レベルの多くの例は、単体テストを使用してアプリケーションのロジックをチェックすることがいかに簡単かを示しています。 ただし、データベース、つまりこれらのほとんどがWebアプリケーションで中心的な役割を果たすアプリケーションをテストする場合、すべてがそれほど単純ではありません。 アプリケーションの単体テストに携わっている人は、データベースのテストの問題に繰り返し直面していると思います。 ほぼ2年前、このテーマに関するHabrの
記事が既にありましたが、それをもっと明らかにしたいと思います。
Dbunit
だから、DbUnit。 もともとは、テストを実行する前にデータベースをセットアップするためのJUnit(Javaアプリケーションの単体テスト用フレームワーク)用に開発されました。 その結果、この拡張機能は開発され、他のxUnitフレームワーク、特にPHPUnitに移行されました。 現在、MySql、PostgreSql、Oracle、およびSqliteがサポートされています。
なぜDbUnitなのか?
アプリケーションとデータベースの相互作用をテストするには、さらに次の手順を実行する必要があります。
- データベースとテーブルの構造を考慮に入れる
- 必要に応じて過去のデータ挿入をフリック
- ビジネスロジックのいずれかの操作を実行した後、データベースの状態を確認するには
- データベースをクリアして、テストごとにもう一度繰り返します(そうでない場合、後続のテストは以前のテストに依存します)
SQLクエリを使用してこれを手動で記述すると、すぐに原則として単体テストを呪うようになります。 さらに、これは単体テストの主な原則の1つに対応していません。テストは最小限に複雑で、最大限に読みやすいものでなければなりません。
順番に
だから、どのように右のは、データベースとの相互作用の試験に合格しなければならないのですか?
- ベースクリーニング 。 最初の起動時には、データベースの状態がわからないため、「ゼロから開始する」義務があります。
- 初期データ(固定具)を挿入します。 通常、アプリケーションには、さらに処理するためにデータベースから取得する初期データが必要です。 これらは、あなたが新しく洗練ベースを使用する必要がありますものです。
- テストとテスト結果の実際のパフォーマンス。 コメントはありません。
PHPUnitのデータベーステストケース
PHPUnitの通常のテストケースの場合、PHPUnit_Framework_TestCaseクラスを継承する必要があるだけの場合、データベースをテストする場合は、すべてが多少複雑になります。
require_once "PHPUnit/Extensions/Database/TestCase.php"; class MyTest extends PHPUnit_Extensions_Database_TestCase { public function getConnection() { $pdo = new PDO('mysql:host=localhost;dbname=testdb', 'root', ''); return $this->createDefaultDBConnection($pdo, 'testdb'); } public function getDataSet() { return $this->createFlatXMLDataSet(dirname(__FILE__).'/_files/guestbook-init.xml'); } }
getConnection()とgetDataSet()の2つの抽象メソッドを実装する必要があります。 1つ目はデータベースとの接続を確立するために必要であり、2つ目はデータベースをテーブルで満たし、実際のテーブルを埋めるためです。
getConnection()はPDOを使用してデータベースに接続する必要がありますが、アプリケーションはPDOを使用してデータベースを照会する必要はないことに注意することが重要です。 getConnection()メソッドによって確立された接続は、テストとアサーションのためにデータベースを準備するためにのみ使用されます。
データベースの初期コンテンツは、PHPUnit_Extensions_Database_DataSet_IDataSetおよびPHPUnit_Extensions_Database_DataSet_IDataTableインターフェイスを使用して抽象化されます。 getDataSet()メソッドは、フィクスチャを取得および挿入するためにsetUp()メソッドによって呼び出されます。 この例では、createFlatXMLDataSet()ファクトリメソッドを使用して、XML表現からデータセットを取得しました。
DataTable&データセット
それは何ですか? これらは、問題の拡張機能の重要な概念です。 DataTableとDataSetは、実際のデータベースのテーブルとレコードの抽象化です。 かなり単純なメカニズムにより、オブジェクトの背後にある実際のデータベースを隠すことができます。これは、さまざまな方法で実装できます。
このような抽象化は、予想されるデータベースの内容と実際の内容を比較するために必要です。 予想されるコンテンツは、抽象化によりさまざまな形式で表すことができます-たとえば、XML、CSV、PHP配列。 データテーブルデータセットインターフェースは、データベースから期待される実際のソースからのデータの比較を可能にします。
また、テストを実行する前に、DataSetとDataTableを使用してデータベースの初期状態を設定します。
以下では、データセットのさまざまなオプションを検討します。
フラットXMLデータセット
これは最も単純な種類のデータセットです。 ルート内の各要素は、データベースの1つのエントリを表します。 要素の名前は、テーブルの名前と、属性と値に対応する必要があります。たとえば、フィールドとフィールド値は、それぞれ次のようになります。
<?xml version="1.0" encoding="UTF-8" ?> <dataset> <post post_id="1" title="My First Post" date_created="2008-12-01 12:30:29" contents="This is my first post" rating="5" /> <post post_id="2" title="My Second Post" date_created="2008-12-04 15:35:25" contents="This is my second post" /> </dataset>
これは、2エントリデータベースの投稿テーブルに相当します
post_id | タイトル | 作成日 | 内容 | 格付け |
---|
1 | 私の最初の投稿 | 2008-12-01 12時30分29秒 | これは私の最初の投稿です | 5 |
2 | 私の2番目の投稿 | 2008-12-04 15時三十五分25秒 | これは私の2番目の投稿です | ヌル |
一般的に、それは非常にシンプルで簡単です。
空のテーブルは、空のテーブルcurrent_visitorsなどの空の要素と同等です。
<?xml version="1.0" encoding="UTF-8" ?> <dataset> <current_visitors /> </dataset>
エントリのNULL値は、対応する属性の欠如として表されます(ブログの評価フィールドの例を参照)が、ここでは1つの点を考慮する必要があります。 フラットXML DataSetの場合、テーブルの構造は最初の要素、つまり 最初の要素に属性がなく、同じテーブルの後続の要素に属性がある場合、これらの属性は無視されます。 例えば、最初の要素から表のブログの例の場合は、その値でDATE_CREATED属性を削除するには、この属性の2番目の要素がテーブルに考慮されることはありませんが、フィールドはDATE_CREATEDされることはありません。
createFlatXmlDataSet()メソッドで使用する:
class MyTestCase extends PHPUnit_Extensions_Database_TestCase { public function getDataSet() { return $this->createFlatXmlDataSet('myFlatXmlFixture.xml'); } }
XMLデータセット
このバージョンのXMLにはFlat XMLの欠点はありませんが、やや複雑です。
<?xml version="1.0" encoding="UTF-8" ?> <dataset> <table name="post"> <column>post_id</column> <column>title</column> <column>date_created</column> <column>contents</column> <column>rating</column> <row> <value>1</value> <value>My First Post</value> <value>2008-12-01 12:30:29</value> <value>This is my first post</value> <value>5</value> </row> <row> <value>2</value> <value>My Second Post</value> <value>2008-12-04 15:35:25</value> <value>This is my second post</value> <null /> </row> </table> </dataset>
テーブルは<table>要素によって完全に表され、<column>はテーブルフィールドを定義するためにネストされ、<row>はレコードを表します。 次に、<value>を<row>に埋め込んで、意味のあるフィールドとNULL値の<null />を表すことができます。
空のテーブルは、<row>要素のないテーブルとして表されます。
<?xml version="1.0" encoding="UTF-8" ?> <dataset> <table name="current_visitors"> <column>current_visitors_id</column> <column>ip</column> </table> </dataset>
createXMLDataSet()メソッドで使用する:
class MyTestCase extends PHPUnit_Extensions_Database_TestCase { public function getDataSet() { return $this->createXMLDataSet('myFlatXmlFixture.xml'); } }
CSVデータセット
CSV形式でのテーブルの表示(カンマ区切り値-テーブルを保存するための最も単純な形式)。 それはかなり明らかです。
post_id,title,date_created,contents,rating
1,My First Post,2008-12-01 12:30:29,This is my first post,5
2,My Second Post,2008-12-04 15:35:25,This is my second post,
使用はXMLよりも少し複雑です:
class MyTestCase extends PHPUnit_Extensions_Database_TestCase { public function getDataSet() { $dataSet = new PHPUnit_Extensions_Database_DataSet_CsvDataSet(); $dataSet->addTable('post', 'post.csv'); return $dataSet; } }
使用するには、クラスPHPUnit_Extensions_Database_DataSet_CsvDataSetのオブジェクトを作成する必要があります。 コンストラクターは、CSV形式を定義する3つの引数を取ります。
public function __construct($delimiter = ',', $enclosure = '"', $escape = '"'){}
その後、addTableメソッドを使用してデータセットにテーブルを追加します-1つのファイル-1つのテーブル。
PHPアレイ
現時点では、配列を使用したデータセットの標準的な実装はありませんが、実装は簡単です;)
次の形式でデータセットを保存する必要があるとします。
array( 'post' => array( array( 'post_id' => 1, 'title' => 'My First Post', 'date_created' => '2008-12-01 12:30:29', 'contents' => 'This is my first post', 'rating' => 5 ), array( 'post_id' => 2, 'title' => 'My Second Post', 'date_created' => '2008-12-04 15:35:25', 'contents' => 'This is my second post', 'rating' => null ), ), )
実装:
require_once 'PHPUnit/Extensions/Database/DataSet/AbstractDataSet.php'; require_once 'PHPUnit/Extensions/Database/DataSet/DefaultTableIterator.php'; require_once 'PHPUnit/Extensions/Database/DataSet/DefaultTable.php'; require_once 'PHPUnit/Extensions/Database/DataSet/DefaultTableMetaData.php'; class ArrayDataSet extends PHPUnit_Extensions_Database_DataSet_AbstractDataSet { protected $tables = array(); public function __construct(array $data) { foreach ($data as $tableName => $rows) { $columns = array(); if (isset($rows[0])) { $columns = array_keys($rows[0]); } $metaData = new PHPUnit_Extensions_Database_DataSet_DefaultTableMetaData($tableName, $columns); $table = new PHPUnit_Extensions_Database_DataSet_DefaultTable($metaData); foreach ($rows as $row) { $table->addRow($row); } $this->tables[$tableName] = $table; } } protected function createIterator($reverse = FALSE) { return new PHPUnit_Extensions_Database_DataSet_DefaultTableIterator($this->tables, $reverse); } }
いくつかのコメント-データセットについては、抽象データセットを継承します(フラットなXML、XML、CSVなどが継承します)。 コンストラクターでは、以前に合意した配列を渡します。 フラットXMLの場合と同様に、テーブル構造は最初のレコードによって決定されますが、この場合、NULL値を明示的に指定できるため、重要ではありません。 ところで、構造は、PHPUnit_Extensions_Database_DataSet_DefaultTableMetaDataオブジェクトを作成することによって決定されます。 その後、テーブル自体を作成し、構造を渡して、addRow()メソッドを使用してテーブルにレコードを追加します。 抽象createIteratorメソッドも実装する必要がありますが、複雑なことは何もありません:)
使用法:
class MyTestCase extends PHPUnit_Extensions_Database_TestCase { public function getDataSet() { return new ArrayDataSet(array( 'post' => array( array( 'post_id' => 1, 'title' => 'My First Post', 'date_created' => '2008-12-01 12:30:29', 'contents' => 'This is my first post', 'rating' => 5 ), array( 'post_id' => 2, 'title' => 'My Second Post', 'date_created' => '2008-12-04 15:35:25', 'contents' => 'This is my second post', 'rating' => null ), ), )); } }
クエリ/データベースデータセット
アサーションの場合、予想されるデータセットだけでなく、データベースからの実際のデータセットも必要です。 QueryDataSetはこれに役立ちます。
$ds = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection()); $ds->addTable('post');
または明示的な要求を使用して:
$ds->addTable('post', 'SELECT * FROM post ORDER BY post_id');
また、自動的にメソッドのPHPUnit_Extensions_Database_DB_DefaultDatabaseConnectionを使用して、既存のテーブルからのデータセットを得るために、既存の接続を使用することができます:: createDataSet()(のgetConnection(で作成されたオブジェクト))。 createDataSet()にパラメーターを渡さない場合、既存のすべてのテーブルからデータセットが作成されます。 あなたは、データベース内のテーブルの名前を持つパラメータの配列を渡す場合、データセットは、これらのテーブルからのみ生成されます。
置換データセット
フラットXMLデータセットのNULL値の問題については既に述べました(CSVの問題は同じです-フィクスチャーにNULL値を明示的に設定することは不可能です)。 これは、特別なデコレーター-ReplacementDataSetを使用して解決できます。
require_once 'PHPUnit/Extensions/Database/DataSet/ReplacementDataSet.php'; class MyTestCase extends PHPUnit_Extensions_Database_TestCase { public function getDataSet() { $ds = $this->createFlatXmlDataSet('myFlatXmlFixture.xml'); $rds = new PHPUnit_Extensions_Database_DataSet_ReplacementDataSet($ds); $rds->addFullReplacement('##NULL##', null); return $rds; } }
##'、NULL); require_once 'PHPUnit/Extensions/Database/DataSet/ReplacementDataSet.php'; class MyTestCase extends PHPUnit_Extensions_Database_TestCase { public function getDataSet() { $ds = $this->createFlatXmlDataSet('myFlatXmlFixture.xml'); $rds = new PHPUnit_Extensions_Database_DataSet_ReplacementDataSet($ds); $rds->addFullReplacement('##NULL##', null); return $rds; } }
XMLで## NULL ##を使用して、NULL値を示すことができます。
<?xml version="1.0" encoding="UTF-8" ?> <dataset> <post post_id="1" title="My First Post" date_created="2008-12-01 12:30:29" contents="This is my first post" rating="5" /> <post post_id="2" title="My Second Post" date_created="2008-12-04 15:35:25" contents="This is my second post" rating="##NULL##" /> </dataset>
データのフィルタリング
大規模なデータセットの場合、DataSetFilterを使用してフィルタリングを適用できます。
require_once 'PHPUnit/Extensions/Database/DataSet/ReplacementDataSet.php'; class MyTestCase extends PHPUnit_Extensions_Database_TestCase { public function testIncludeFilteredPost() { $dataSet = $this->getConnection()->createDataSet(); $filterDataSet = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($dataSet); $filterDataSet->addIncludeTables(array('post')); $filterDataSet->setIncludeColumnsForTable('post', array('post_id', 'title'));
- > createDataSet(); require_once 'PHPUnit/Extensions/Database/DataSet/ReplacementDataSet.php'; class MyTestCase extends PHPUnit_Extensions_Database_TestCase { public function testIncludeFilteredPost() { $dataSet = $this->getConnection()->createDataSet(); $filterDataSet = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($dataSet); $filterDataSet->addIncludeTables(array('post')); $filterDataSet->setIncludeColumnsForTable('post', array('post_id', 'title'));
- > createDataSet(); require_once 'PHPUnit/Extensions/Database/DataSet/ReplacementDataSet.php'; class MyTestCase extends PHPUnit_Extensions_Database_TestCase { public function testIncludeFilteredPost() { $dataSet = $this->getConnection()->createDataSet(); $filterDataSet = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($dataSet); $filterDataSet->addIncludeTables(array('post')); $filterDataSet->setIncludeColumnsForTable('post', array('post_id', 'title'));
最初のケースでは、我々は唯一のデータセットのテーブルに残されているだけにpost_idとタイトルのフィールドにそのレコードの内容を投稿してください。 第二に - 私たちは、フィールドのDATE_CREATED」と「評価」のテーブル掃除値からデータセットテーブル「FOO」、「バー」と「バズ」、およびポストエントリーから除外しています。
データセットの構成
複数のデータセットを1つに結合できます。 データセットが同じテーブルを持っている場合、レコードはそれらに追加されます、例えば:
データセット-1.xml
<?xml version="1.0" encoding="UTF-8" ?> <dataset> <post post_id="1" title="My First Post" date_created="2008-12-01 12:30:29" contents="This is my first post" rating="5" /> </dataset>
データセット-2.xml
<?xml version="1.0" encoding="UTF-8" ?> <dataset> <post post_id="2" title="My Second Post" date_created="2008-12-04 15:35:25" contents="This is my second post" /> </dataset>
それらを集約します。
class MyTestCase extends PHPUnit_Extensions_Database_TestCase { public function getDataSet() { $ds1 = $this->createFlatXmlDataSet('dataset-1.xml'); $ds2 = $this->createFlatXmlDataSet('dataset-2.xml'); $compositeDs = new PHPUnit_Extensions_Database_DataSet_CompositeDataSet(); $compositeDs->addDataSet($ds1); $compositeDs->addDataSet($ds2); return $compositeDs; } }
アサート
多くの場合、テーブル内のエントリの数を確認する必要があります。 これは、通常のassertEqualsを使用して実行できます。
class MyTestCase extends PHPUnit_Extensions_Database_TestCase { public function testAddEntry() { $this->assertEquals(2, $this->getConnection()->getRowCount('post')); $blog = new Blog(); $blog->addPost("My third post.", "This is my third post."); $this->assertEquals(3, $this->getConnection()->getRowCount('post')); } }
、 "これは私の第三のポストである。"ポスト。"); class MyTestCase extends PHPUnit_Extensions_Database_TestCase { public function testAddEntry() { $this->assertEquals(2, $this->getConnection()->getRowCount('post')); $blog = new Blog(); $blog->addPost("My third post.", "This is my third post."); $this->assertEquals(3, $this->getConnection()->getRowCount('post')); } }
getRowCount()メソッドは、指定されたテーブルのレコード数を返します。
テーブルを比較するには、assertTablesEqual()メソッドを使用します。
class MyTestCase extends PHPUnit_Extensions_Database_TestCase { public function testTables() { $queryTable = $this->getConnection()->createQueryTable('post', 'SELECT * FROM post'); $expectedTable = $this->createFlatXmlDataSet("myFlatXmlFixture.xml")->getTable("post"); $this->assertTablesEqual($expectedTable, $queryTable); } }
日付をチェックするときにテストが失敗する可能性があることを覚えておく必要があります-フィクスチャーに設定された日付があり、現在の時間がデータベースに書き込まれている場合、これらの日付が一致しない場合はファイルを受け取ります したがって、期待される結果から日付が削除されることが多く、それに応じて、実際のデータセットの受信が変更されます。
$queryTable = $this->getConnection()->createQueryTable('post', 'SELECT post_id, title, date_created, contents, rating FROM post');
最後に、assertDataSetsEqual()を使用してデータセットを直接比較できます。
class MyTestCase extends PHPUnit_Extensions_Database_TestCase { public function testTables() { $dataSet = $this->getConnection()->createDataSet(array('post')); $expectedDataSet = $this->createFlatXmlDataSet("myFlatXmlFixture.xml"); $this->assertDataSetsEqual($expectedDataSet, $dataSet); } }
この記事の大部分は
、Benjamin Eberleiの記事「PHPUnitによるデータベーステストの究極のガイド」と、もちろん
公式マニュアルに基づいて書かれてい
ます 。