Database Fixture Tools

The DatabaseFixtures class is an AperiTestCase fixture-decorator used to test data access classes with a real database and real data set up and torn down for each test.

Note that DataAccessFixtureTools will only work with an Aperiplus DatabaseConnection object (see below). That probably limits the potential user-base to just one—me—but the underlying ideas may be of interest to other test-infected programmers and so here it is. Feel free to pinch anything for your own test framework.

<?php    

interface iDatabaseConnection {
    function 
ping();
    function 
close();
    function 
query();           // anything else not listed below
    
function getRow();          // select (single row) returns array
    
function getRows();         // select (indeterminate number of rows) returns iterator
    
function editTable();       // delete, insert, update returns num affected
    
function lastInsertId();
    function 
getError();
}

Disclaimer

Although I have been using various versions of this for some time without any problems, it's up to you to check the code as thoroughly as you need for your own purposes. Data is precious.

In Use

Extend AperiUnitTest with your own test case class and add a beforeInvoking() method:

<?php    

class YourTests extends AperiUnitTest {

     function 
beforeInvoking() {
        
$this->mixin('DatabaseFixtures');
    }

You've got a choice of "DatabaseFixtures" or "TableFixtures". Normally you'd use the former. DatabaseFixtures assumes you have create database privileges and hence can set up and tear down whole databases as test fixtures. TableFixtures is used when you don't have the create database privilege (eg a budget hosting account). Test fixtures are comprised of tables set up and torn down within the sole available database.

As with all test case decorators, the methods of the chosen class are magically added to the test case.

Now you can add a host:

<?php    

class YourTests extends AperiUnitTest {

     function 
beforeInvoking() {
        
$this->mixin('DatabaseFixtures');
        
$this->addHost(
            
'MysqliConnection'
            
'host'
            
'user'
            
'pass');
    }

The first parameter is the name of a class which implements the Aperiplus iDatabaseConnection interface (only mysql is supported at the moment). Currently you can only add a single database server but in future versions I'll look at support for multiple hosts.

If you've chosen TableFixtures, also pass the database name in addHost:

<?php    

class YourTests extends AperiUnitTest {

     function 
beforeInvoking() {
        
$this->mixin('TableFixtures');
        
$this->addHost(
            
'MysqliConnection'
            
'host'
            
'user'
            
'pass'
            
'database-name');
    }

To do: charset issues?

Create A Fixture

Special "SandboxedConnection" objects are provided for testing. These add a query authorisation wrapper to the standard connection.

The idea is that data access tests will be isolated in a sandbox thus protecting any non-test data on the server. On a local development box, malfunctioning code can't interfere with other projects or, on a production server, you can run a few tests without risking live data.

The fixture tool, $this->fxtool(), is used solely for fixture creation:

<?php    

class YourTests extends AperiUnitTest {
    ...

    function 
setUp() {
        
$this->fxtool()->query("create database foo");
        
$this->fxtool()->query("create table foo.bar (a text)");
        ...
        ...
    }

With DatabaseFixtures, All sandboxed connections can be told to use a default database with a single command. Don't forget to create it first. (With TableFixtures, all connections automatically use the named db).

<?php    

class YourTests extends AperiUnitTest {
    ...

    function 
setUp() {
        
$this->fxtool()->query("create database foo");
        
$this->useDb('foo');
        ...
        ...
    }

If the fixture is complicated you might want to write (and test) an installer:

<?php    

class YourTests extends AperiUnitTest {
    ...

    function 
setUp() {
        
$installer = new MyDatabaseSchema(
            
$this->fxtool(),
            
'database_name');
        
$installer->install();
     }

Or you could register a test case fixture. Fixture objects can easily be re-used in other tests (as can an installer).

<?php

class FooFixture {

    function 
__construct($conn) {
        
$this->_conn $conn;
    }
    function 
setUp() {
        
$this->_conn->query->(...);
        
$this->_conn->query->(...);
    }
}

class 
YourTests extends AperiUnitTest {

     function 
beforeInvoking() {
        
$this->mixin(...);
        
$this->addHost(...);
        
$this->addFixture(new FooFixture($this->fxtool()));
     }

The fixture object will be plugged in to the setUp/tearDown cycle. See docs: Decorating Test Cases.

Note that a tearDown method isn't required when you use the fixture tool to create databases or tables. Everything created with the fixture tool (or any other sandboxed connection) during a test is automatically dropped after the test is run.

Test

Use getConnection() / getConn() to obtain a fresh (sandboxed) connection object for each instance of the tested class. Each call to getConnection() returns a new object encapsulating a unique database connection.

<?php    

    
function testSomething() {
        
$testable = new YourClass($this->getConn());
        ...
        ...
    }

By default, the connection object will connect using the account you gave in the addHost call, above. You can choose another:

<?php    

    
function testSomething() {
        
$testable = new YourClass($this->getConn('user''pass'));
        ...
        ...
    }

At last, we can do some testing. You've got all the usual AperiUnitTest assertions plus:

If you need to assert data stored in a table you can simplify the code a little with a custom assertion rather than using assertRow/assertRows. Tuck the select query away in the assert method:

<?php    

    
function assertFooRecords() {
        
$args func_get_args();
        
array_unshift($args"select * from foo");
        return 
call_user_func_array(
            array(
$this'assertRows'), 
            
$args);
    }

In use:

<?php    

    
function test() {
        ...
        
$this->assertFooRecords(
            array(
'id'=>1'name'=>'foo'), 
            array(
'id'=>2'name'=>'bar'), 
            
// ... 
            // (variable num args, 1 per expected row)
            
);
    }

Tear Down

If you use the sandboxed connections, the test case will automatically tidy up after itself. All fixture databases (or tables) are dropped after each test and all connections are closed (except for the fixture tool which is closed after all tests in the current test case have run). You won't need a tearDown method unless you have other, non-database items to clean up.

A fatal error in the tested class means that the test case will die before it gets a chance to tidy up. To avoid having to delete these vestigial fixtures manually (new tests must always start from a clean slate) they will be detected and dropped automatically the next time the test script is run (in fact the next time any DatabaseFixtures-decorated test is run: the same persistence mechanism is used for all and hence any one will remove fixtures left behind by any other).

Skipping

Built-in skip conditions:

SourceForge.net Logo