Agile Zone is brought to you in partnership with:

I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 638 posts at DZone. You can read more from them at their website. View Full User Profile

Practical PHP Testing Patterns: Shared Fixture

11.15.2010
| 4290 views |
  • submit to reddit

The Shared Fixture pattern is the last of the Fixture organization patterns treated in this series. A Shared Fixture is the opposite and specular case of a Fresh Fixture: it is not recreated for each test, but all the test methods reuse the same object graph or external resource.

As long as the PHP process is the same, you can share any kind of resource between tests, even if it's not an object but a resource like a database connection (now transitioned to object in PDO).

The first test, however, will be the one lazily setting up the fixture and to preserve independent running of tests. This mechanism is needed to preserve the ability to run a single test in isolation.

Pros & cons

Shared Fixtures are by far faster then Fresh one: the fixture is created only one time, in an operation of complexity O(1) instead of O(N), where N is the number of tests. When the fixtures comprehend a database connection with tens of tables, it makes a big difference if it is bootstrapped one time or before each test; whereas it has usually no state that can be altered by a test to affect the subsequent ones, apart from the column structure of tables.

The issues with Shared Fixtures are essentially what you would get from a Fresh Fixture instead: total isolation of your tests from each other. For example the data inserted into tables of a shared connection must be managed if you want to reuse the same database schema in more than one test. There are specific patterns to reset a database between tests, but this applied to every kind of Shared Fixture.

Implementation

The architecture of PHPUnit requires (rightfully) to use class static variables for setting up and maintaining references to Shared Fixtures. This behavior is determined y the fact that PHPUnit creates a new Test Case object for each test methods, in order to run it in isolation.

You have two options for creating a Shared Fixture:

  • you can set it up it in a lazy-creation method, which checks if the static variable containing the fixture already has a value, and recreates it only if it's not the case; then it stores the result in the static variable before returning. This is a classic Lazy Loading pattern, and We do this with a Doctrine 2 EntityManager, which has a reference to a database connection inside.
  • a bootstrap file can set up fixtures for you, as it is executed only one time in each PHPUnit process, before tests are run. Bootstrap files are commonly used for setting up other shared state like autoloading mechanisms.

However, a bootstrap file cannot select what to set up, and if you have a general purpose one it will create fixtures for every test. This may not perform well when running a single test, or a small group of tests, which do not need all the possible fixtures to run. Imagine starting a Selenium server in the boostrap file...

Variations

There are some variations to this pattern, to simplify its usage or leveraging its speed.

An Immutable Shared Fixture is a version of this pattern that lowers the risk associated to shared state between tests. Make the fixture objects immutable is not always possible however.

You can also build a test chain over a static fixture, which is however the same solution as an assertion roulette over a single SUT. Many assertions in the same test use obviously only one shared fixture, but the problem is they are not independent from each other: if multiple ones fail, you'll see only the first one to fail, which throws an exception. If the SUT creation is costly in terms of resources (time and space) and the assertions do not affect each other, you can separate them in several test methods, which all target the same Shared Fixture.

Example

The sample code shows how to share an in-memory SQLite database between tests.

SQLite in-memory database are the fastest database you can use in a test suite (even given that it throws away referential constraints, which are not supported by the engine.) However, creating a large number of tables and even a new connection is still slower than resetting the existing ones.

Database connections are in fact the classic case where Shared Fixture saves the day. However, the isolation of tests must be preserved and you must make sure that all the tables are truncated or returned to their original state in the tearDown(). There are more specific patterns to reset the shared database: here I will show only its lazy creation.

<?php

class SharedFixtureDatabaseTest extends PHPUnit_Framework_TestCase
{
    /**
     * This is global state, beware!
     * Use the static keyword responsibly.
     * @var PDO
     */
    private static $db;

    /**
     * If you want to share the Db in more than one test case, you
     * can just make this method public.
     * @return PDO
     */
    protected static function getDbConnection()
    {
        if (is_null(self::$db)) {
            self::$db = new PDO('sqlite::memory:');
            self::$db->exec('CREATE TABLE users (
                            id INT, 
                            name VARCHAR(255),
                            password VARCHAR(255),
                            PRIMARY KEY (id)
                       )');
        }

        return self::$db;
    }

    public function testFirstTestThatNeedsAConnection()
    {
        $sut = new SUT($this->getDbConnection(), 'option');
    }

    public function testSecondTestThatNeedsAConnection()
    {
        $sut = new SUT($this->getDbConnection(), 'anotherOption');
    }
}

/**
 * A sample System Under Test which requires a connection.
 */
class SUT
{
    public function __construct(PDO $connection, $configurationOption)
    {
        // ...
    }
} 
Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)