| Prev | Next |
作成中のソフトウェアのテストを書いているうちに、 データベースに関するコードをテストする必要が出てくることもあるでしょう。 データベース拡張を使用すると、 データベースを特定の状態にしてからデータベース関連のコードを実行し、 データベースのデータが期待通りになっているかどうかを確かめることができます。
データベース関連のユニットテストを作成するもっとも手っ取り早い方法は、 PHPUnit_Extensions_Database_TestCase クラスを継承することです。 このクラスには、データベース接続を作成したり データベースにデータを送信したり、 テストの実行後にデータベースの中身を様々な形式のデータセットと比較したりする機能があります。 例 9.1 に、getConnection() と getDataSet() の実装例を示します。
例 9.1: データベーステストケースの準備
<?php
require_once 'PHPUnit/Extensions/Database/TestCase.php';
class DatabaseTest extends PHPUnit_Extensions_Database_TestCase
{
protected function getConnection()
{
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'root', '');
return $this->createDefaultDBConnection($pdo, 'testdb');
}
protected function getDataSet()
{
return $this->createFlatXMLDataSet(dirname(__FILE__).'/_files/bank-account-seed.xml');
}
}
?>
getConnection() メソッドは、 PHPUnit_Extensions_Database_DB_IDatabaseConnection インターフェイスの実装を返す必要があります。 createDefaultDBConnection() メソッドを使用して、データベース接続を返すことができます。 このメソッドの最初のパラメータには PDO オブジェクトを渡し、2 番目のパラメータにはテスト対象のスキーマの名前を渡します。
getDataSet() メソッドは、 PHPUnit_Extensions_Database_DataSet_IDataSet インターフェイスの実装を返す必要があります。 現在、PHPUnit では 3 種類のデータセットが使用できます。 データセットについては 「データセット」 で説明します。
表9.1 データベーステストケースのメソッド
| メソッド | 意味 |
|---|---|
PHPUnit_Extensions_Database_DB_IDatabaseConnection getConnection()
|
データベース接続を返すように実装します。これを用いて、期待するデータセットやテーブルを調べます。 |
PHPUnit_Extensions_Database_DataSet_IDataSet getDataSet()
|
データセットを返すように実装します。データベースの初期設定 (setup) や後始末 (teardown) の際にこれを使用します。 |
PHPUnit_Extensions_Database_Operation_DatabaseOperation getSetUpOperation()
|
オーバーライドして、各テストの最初に実行する特定の操作を返すようにします。操作の詳細については 「操作」 を参照ください。 |
PHPUnit_Extensions_Database_Operation_DatabaseOperation getTearDownOperation()
|
オーバーライドして、各テストの最後に実行する特定の操作を返すようにします。操作の詳細については 「操作」 を参照ください。 |
PHPUnit_Extensions_Database_DB_DefaultDatabaseConnection createDefaultDBConnection(PDO $connection, string $schema)
|
$connection PDO オブジェクトのデータベース接続ラッパーを返します。テスト対象のデータベーススキーマを $schema で指定します。このメソッドの結果を getConnection() の返り値として使用することができます。
|
PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet createFlatXMLDataSet(string $xmlFile)
|
$xmlFile で指定した絶対パスにある XML ファイルから作成したフラット XML データセットを返します。XML ファイルについての詳細は 「Flat XML データセット」 を参照ください。このメソッドの結果を getDataSet() の返り値として使用することができます。
|
PHPUnit_Extensions_Database_DataSet_XmlDataSet createXMLDataSet(string $xmlFile)
|
$xmlFile で指定した絶対パスにある XML ファイルから作成した XML データセットを返します。XML ファイルについての詳細は 「XML データセット」 を参照ください。このメソッドの結果を getDataSet() の返り値として使用することができます。
|
void assertTablesEqual(PHPUnit_Extensions_Database_DataSet_ITable $expected, PHPUnit_Extensions_Database_DataSet_ITable $actual)
|
$expected テーブルの内容が $actual テーブルの内容と一致しないときにエラーを報告します。
|
void assertDataSetsEqual(PHPUnit_Extensions_Database_DataSet_IDataSet $expected, PHPUnit_Extensions_Database_DataSet_IDataSet $actual)
|
$expected データセットの内容が $actual データセットの内容と一致しないときにエラーを報告します。
|
Data sets are the basic building blocks for both your database fixture as well as the assertions you may make at the end of your test. When returning a data set as a fixture from the PHPUnit_Extensions_Database_TestCase::getDataSet() method, the default implementation in PHPUnit will automatically truncate all tables specified and then insert the data from your data set in the order specified by the data set. For your convenience there are several different types of data sets that can be used at your convenience.
The flat XML data set is a very simple XML format that uses a single XML element for each row in your data set. An example of a flat XML data set is shown in 例 9.2.
例 9.2: Flat 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"
/>
<post
post_id="2"
title="My Second Post"
date_created="2008-12-04 15:35:25"
contents="This is my second post"
/>
<post
post_id="3"
title="My Third Post"
date_created="2008-12-09 03:17:05"
contents="This is my third post"
rating="3"
/>
<post_comment
post_comment_id="2"
post_id="2"
author="Frank"
content="That is because this is simply an example."
url="http://phpun.it/"
/>
<post_comment
post_comment_id="1"
post_id="2"
author="Tom"
content="While the topic seemed interesting the content was lacking."
/>
<current_visitors />
</dataset>
As you can see the formatting is extremely simple. Each of the elements within the root <dataset> element represents a row in the test database with the exception of the last <current_visitors /> element (this will be discussed shortly.) The name of the element is the equivalent of a table name in your database. The name of each attribute is the equivalent of a column name in your databases. The value of each attribute is the equivalent of the value of that column in that row.
This format, while simple, does have some special considerations. The first of these is how you deal with empty tables. With the default operation of CLEAN_INSERT you can specify that you want to truncate a table and not insert any values by listing that table as an element without any attributes. This can be seen in 例 9.2 with the <current_visitors /> element. The most common reason you would want to ensure an empty table as a part of your fixture is when your test should be inserting data to that table. The less data your database has, the quicker your tests will run. So if I where testing my simple blogging software's ability to add comments to a post, I would likely change 例 9.2 to specify post_comment as an empty table. When dealing with assertions it is often useful to ensure that a table is not being unexpectedly written to, which could also be accomplished in FlatXML using the empty table format.
The second consideration is how NULL values are defined. The nature of the flat XML format only allows you to explicitly specify strings for column values. Of course your database will convert a string representation of a number or date into the appropriate data type. However, there are no string representations of NULL. You can imply a NULL value by leaving a column out of your element's attribute list. This will cause a NULL value to be inserted into the database for that column. This leads me right into my next consideration that makes implicit NULL values somewhat difficult to deal with.
The third consideration is how columns are defined. The column list for a given table is determined by the attributes in the first element for any given table. In 例 9.2 the post table would be considered to have the columns post_id, title, date_created, contents and rating. If the first <post> were removed then the post would no longer be considered to have the rating column. This means that the first element of a given name defines the structure of that table. In the simplest of examples, this means that your first defined row must have a value for every column that you expect to have values for in the rest of rows for that table. If an element further into your data set specifies a column that was not specified in the first element then that value will be ignored. You can see how this influenced the order of elements in my dataset in 例 9.2. I had to specify the second <post_comment> element first due to the non-NULL value in the url column.
There is a way to work around the inability to explicitly set NULL in a flat XML data using the Replacement data set type. This will be discussed further in 「データセットの交換」.
While the flat XML data set is simple it also proves to be limiting. A more powerful xml alternative is the XML data set. It is a more structured xml format that allows you to be much more explicit with your data set. An example of the XML data set equivalent to the previous flat XML example is shown in 例 9.3.
例 9.3: 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>
<row>
<value>3</value>
<value>My Third Post</value>
<value>2008-12-09 03:17:05</value>
<value>This is my third post</value>
<value>3</value>
</row>
</table>
<table name="post_comment">
<column>post_comment_id</column>
<column>post_id</column>
<column>author</column>
<column>content</column>
<column>url</column>
<row>
<value>1</value>
<value>2</value>
<value>Tom</value>
<value>While the topic seemed interesting the content was lacking.</value>
<null />
</row>
<row>
<value>2</value>
<value>2</value>
<value>Frank</value>
<value>That is because this is simply an example.</value>
<value>http://phpun.it</value>
</row>
</table>
<table name="current_visitors">
<column>current_visitors_id</column>
<column>ip</column>
</table>
</dataset>
The formatting is more verbose than that of the Flat XML data set but in some ways much easier to understand. The root element is again the <dataset> element. Then directly under that element you will have one or more <table> elements. Each <table> element must have a name attribute with a value equivalent to the name of the table being represented. The <table> element will then include one or more <column> elements containing the name of a column in that table. The <table> element will also include any number (including zero) of <row> elements. The <row> element will be what ultimately specifies the data to store/remove/update in the database or to check the current database against. The <row> element must have the same number of children as there are <column> elements in that <table> element. The order of your child elements is also determined by the order of your <column> elements for that table. There are two different elements that can be used as children of the <row> element. You may use the <value> element to specify the value of that column in much the same way you can use attributes in the flat XML data set. The content of the <value> element will be considered the content of that column for that row. You may also use the <null> element to explicitly indicate that the column is to be given a NULL value. The <null> element does not contain any attributes or children.
You can use the DTD in 例 9.4 to validate your XML data set files. A reference of the valid elements in an XML data set can be found in 表 9.2
例 9.4: The XML Data Set DTD
<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT dataset (table+) | ANY>
<!ELEMENT table (column*, row*)>
<!ATTLIST table
name CDATA #REQUIRED
>
<!ELEMENT column (#PCDATA)>
<!ELEMENT row (value | null | none)*>
<!ELEMENT value (#PCDATA)>
<!ELEMENT null EMPTY>
表9.2 XML Data Set Element Description
| Element | Purpose | Contents | Attributes |
|---|---|---|---|
<dataset>
|
The root element of the xml data set file. |
One or more <table> elements.
|
None |
<table>
|
Defines a table in the dataset. |
One or more <column> elements and zero or more <row> elements.
|
name - The name of the table.
|
<column>
|
Defines a column in the current table. | A text node containing the name of the column. | None |
<row>
|
Defines a row in the table. |
One or more <value> or <null> elements.
|
None |
<value>
|
Sets the value of the column in the same position as this value. | A text node containing the value of the corresponding column. | None |
<null>
|
Sets the value of the column in the same position of this value to NULL.
|
None | None |
While XML data sets provide a convenient way to structure your data, in many cases they can be time consuming to hand edit as there are not very many XML editors that provide an easy way to edit tabular data via xml. In these cases you may find the CSV data set to be much more useful. The CSV data set is very simple to understand. Each CSV file represents a table. The first row in the CSV file must contain the column names and all subsequent rows will contain the data for those columns.
To construct a CSV data set you must instantiate the PHPUnit_Extensions_Database_DataSet_CsvDataSet class. The constructor takes three parameters: $delimiter, $enclosure and $escape. These parameters allow you to specify the exact formatting of rows within your CSV file. So this of course means it doesn't have to really be a CSV file. You can also provide a tab delimited file. The default constructor will specify a comma delimited file with fields enclosed by a double quote. If there is a double quote within a value it will be escaped by an additional double quote. This is as close to an accepted standard for CSV as there is.
Once your CSV data set class is instantiated you can use the method addTable() to add CSV files as tables to your data set. The addTable method takes two parameters. The first is $tableName and contains the name of the table you are adding. The second is $csvFile and contains the path to the CSV you will be using to set the data for that table. You can call addTable() for each table you would like to add to your data set.
In 例 9.5 you can see an example of how three CSV files can be combined into the database fixture for a database test case.
例 9.5: CSV Data Set Example
--- fixture/post.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,
3,My Third Post,2008-12-09 03:17:05,This is my third post,3
--- fixture/post_comment.csv ---
post_comment_id,post_id,author,content,url
1,2,Tom,While the topic seemed interesting the content was lacking.,
2,2,Frank,That is because this is simply an example.,http://phpun.it
--- fixture/current_visitors.csv ---
current_visitors_id,ip
--- DatabaseTest.php ---
<?php
require_once 'PHPUnit/Extensions/Database/TestCase.php';
require_once 'PHPUnit/Extensions/Database/DataSet/CsvDataSet.php';
class DatabaseTest extends PHPUnit_Extensions_Database_TestCase
{
protected function getConnection()
{
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'root', '');
return $this->createDefaultDBConnection($pdo, 'testdb');
}
protected function getDataSet()
{
$dataSet = new PHPUnit_Extensions_Database_DataSet_CsvDataSet();
$dataSet->addTable('post', 'fixture/post.csv');
$dataSet->addTable('post_comment', 'fixture/post_comment.csv');
$dataSet->addTable('current_visitors', 'fixture/current_visitors.csv');
return $dataSet;
}
}
?>
Unfortunately, while the CSV dataset is appealing from the aspect of edit-ability, it has the same problems with NULL values as the flat XML dataset. There is no native way to explicitly specify a null value. If you do not specify a value (as I have done with some of the fields above) then the value will actually be set to that data type's equivalent of an empty string. Which in most cases will not be what you want. I will address this shortcoming of both the CSV and the flat XML data sets shortly.
| Prev | Next |
assertArrayHasKey()
assertClassHasAttribute()
assertClassHasStaticAttribute()
assertContains()
assertContainsOnly()
assertEqualXMLStructure()
assertEquals()
assertFalse()
assertFileEquals()
assertFileExists()
assertGreaterThan()
assertGreaterThanOrEqual()
assertLessThan()
assertLessThanOrEqual()
assertNull()
assertObjectHasAttribute()
assertRegExp()
assertSame()
assertSelectCount()
assertSelectEquals()
assertSelectRegExp()
assertStringEndsWith()
assertStringEqualsFile()
assertStringStartsWith()
assertTag()
assertThat()
assertTrue()
assertType()
assertXmlFileEqualsXmlFile()
assertXmlStringEqualsXmlFile()
assertXmlStringEqualsXmlString()
Copyright © 2005-2011 Sebastian Bergmann.