Prev | Next |
While creating tests for your software you may come across database code that needs to be unit tested. The database extension has been created to provide an easy way to place your database in a known state, execute your database-effecting code, and ensure that the expected data is found in the database.
The quickest way to create a new Database Unit Test is to extend the
PHPUnit_Extensions_Database_TestCase
class. This class
provides the functionality to create a database connection, seed your
database with data, and after executing a test comparing the contents of
your database with a data set that can be built in a variety of formats. In
Example 9.1 you can see
examples of getConnection()
and getDataSet()
implementations.
Example 9.1: Setting up a database test case
<?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');
}
}
?>
The getConnection()
method must return an
implementation of the PHPUnit_Extensions_Database_DB_IDatabaseConnection
interface. The createDefaultDBConnection()
method can
be used to return a database connection. It accepts a PDO
object as the first parameter and the name of the schema you are testing
against as the second parameter.
The getDataSet()
method must return an implementation of
the PHPUnit_Extensions_Database_DataSet_IDataSet
interface. There are currently three different data sets available in
PHPUnit. These data sets are discussed in the section called “Data Sets”
Table 9.1. Database Test Case Methods
Method | Meaning |
---|---|
PHPUnit_Extensions_Database_DB_IDatabaseConnection getConnection() | Implement to return the database connection that will be checked for expected data sets and tables. |
PHPUnit_Extensions_Database_DataSet_IDataSet getDataSet() | Implement to return the data set that will be used in in database set up and tear down operations. |
PHPUnit_Extensions_Database_Operation_DatabaseOperation getSetUpOperation() | Override to return a specific operation that should be performed on the test database at the beginning of each test. The various operations are detailed in the section called “Operations”. |
PHPUnit_Extensions_Database_Operation_DatabaseOperation getTearDownOperation() | Override to return a specific operation that should be performed on the test database at the end of each test. The various operations are detailed in the section called “Operations”. |
PHPUnit_Extensions_Database_DB_DefaultDatabaseConnection createDefaultDBConnection(PDO $connection, string $schema) | Return a database connection wrapper around the $connection PDO object. The database schema being tested against should be specified by $schema . The result of this method can be returned from getConnection() . |
PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet createFlatXMLDataSet(string $xmlFile) | Returns a flat XML data set that is created from the XML file located at the absolute path specified in $xmlFile . More details about flat XML files can be found in the section called “Flat XML Data Set”. The result of this method can be returned from getDataSet() . |
PHPUnit_Extensions_Database_DataSet_XmlDataSet createXMLDataSet(string $xmlFile) | Returns a XML data set that is created from the XML file located at the absolute path specified in $xmlFile . More details about XML files can be found in the section called “XML Data Set”. The result of this method can be returned from getDataSet() . |
void assertTablesEqual(PHPUnit_Extensions_Database_DataSet_ITable $expected, PHPUnit_Extensions_Database_DataSet_ITable $actual) | Reports an error if the contents of the $expected table do not match the contents in the $actual table. |
void assertDataSetsEqual(PHPUnit_Extensions_Database_DataSet_IDataSet $expected, PHPUnit_Extensions_Database_DataSet_IDataSet $actual) | Reports an error if the contents of the $expected data set do not match the contents in the $actual data set. |
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 Example 9.2.
Example 9.2: A Flat XML Data Set
<?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
Example 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
Example 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 Example 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 Example 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
the section called “Replacement Data Set”.
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 Example 9.3.
Example 9.3: A XML Data Set
<?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 Example 9.4 to validate your XML data set files. A reference of the valid elements in an XML data set can be found in Table 9.2
Example 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>
Table 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 Example 9.5 you can see an example of how three CSV files can be combined into the database fixture for a database test case.
Example 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()
assertNotNull()
assertObjectHasAttribute()
assertRegExp()
assertSame()
assertSelectCount()
assertSelectEquals()
assertSelectRegExp()
assertStringEndsWith()
assertStringEqualsFile()
assertStringStartsWith()
assertTag()
assertThat()
assertTrue()
assertType()
assertXmlFileEqualsXmlFile()
assertXmlStringEqualsXmlFile()
assertXmlStringEqualsXmlString()
Copyright © 2005-2009 Sebastian Bergmann.