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: Minimal Fixture

11.04.2010
| 7598 views |
  • submit to reddit

Fixtures are the set of data that must be loaded into the system before a test, to ensure that the initial state is correct before we start exercising code. Fixtures can range from the values of a few fields in our objects to row in a database.

The Minimal Fixture pattern is one of the ways to manage fixtures, and prescribes to use a simple and economic strategy: create the smallest and simplest fixture possible for a test.
It seems obvious, but we have seen cases in the past, such as tests for Doctrine 1 adapters, where each tests load dozens of objects into the database. These data is defined in a Yaml file, which is parsed and loaded at once.

Disadvantages of one size fits all fixtures

The architecture of Doctrine 1 indeed requires the Yaml fixture to be loaded is a single shot, due to the relationships between object kept in different files. There are some hacks for loading only some files at a time (like linking objects with foreign keys instead of relationships) but at least an entire file must be loaded.

Thus what are the disadvantages of loading a shared, large fixture? There are plenty of them:

  • first, the tests become slower: for each test you have to reconstitute a known state, and that's correct. But using the same state for every test means it must comprehend data necessary to each of them. In the case of database state, this can grow to insert many rows in different tables which are not even touched by the current test.
  • second, it renders the test unclear: additional information can be mistakenly interpreted as necessary to execute the same scenario presented in the test. When you use tests as the first documentation tool for your system, this situation is misleading: would you include the same code in the developer's manual to render your point?
  • finally, tests with Minimal Fixtures are easier to understand, because there's less going on.

This principle is the equivalent of YAGNI (You Aren't Gonna Need It) in the production code; when you apply YAGNI to test code, you get to eliminate all the unnecessary noise.

While TDDing

In Test-Driven Development, you always start your development cycle with a failing test.

If you can already get a failing test, with a clear error message, you shouldn't include additional data unless you think it is required in the contract of the class. Fixtures are part of the dependency of a test, so you may include it because you think the object should use them.

However, if you suspect a fixture is unnecessary, once you get the test green there is a simple heuristic to eliminate it: comment it out and try run the test again. If the test remains green you can delete it altogether (you may add another test which exercises the feature in question if you feel it addressed an important scenario).

For example, we once were writing functional tests for controllers, and we started setting up a set of permissions for the current user so that he could access the actions. However, if the user is a super user (a kind of root), permissions were not checked, so once the tests were green these fixtures could be eliminated without hassle.

The resulting tests were much clear to read and understand, and faster to run since they did not insert in the in-memory database unneeded rows. Multiplied for tens of tests, loading unnecessary fixtures can easily slow down the test suite linearly.

Other types of fixtures

Fixtures are not only for databases: the initial state of an object graph under test is a fixture.

The state of an object is the simplest example of fixture you can think of, and it adheres to the principle of the Minimal Fixture: you only need one or two element in a collection to test removal, and only zero or one to test addition. Everything else is an unnecessary fixture that makes debugging the code more complex in case there is a test failure (and if you Test-Driven Develop your code, there will be.)

Fields set into a domain object usually get out of hand too: you really need to valorize each of the user fields to test a query that filter a single one?

Some constructs, like mock expectations, are not prone to sharing and keeping them in a setUp() feels strange. Other code in the setUp(), such as the creation of the object, may be acceptable; anyway, this pattern is about single, segregated fixtures; there are other fixture-related patterns that work in a different way.

Examples

Here is an example where the System Under Test is a Table Data Gateway:

<?php
class BookTable
{
private $connection;

public function __construct(PDO $connection)
{
$this->connection = $connection;
}

public function addBook($title)
{
$stmt = $this->connection->prepare('INSERT INTO books (title) VALUES (:title)');
$stmt->bindValue(':title', $title, PDO::PARAM_STR);
return $stmt->execute();
}

public function getBooks()
{
$stmt = $this->connection->prepare('SELECT * FROM books');
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

public function deleteBook($title)
{
$stmt = $this->connection->prepare('DELETE FROM books WHERE title = :title');
$stmt->bindValue(':title', $title, PDO::PARAM_STR);
return $stmt->execute();
}
}

class BookTableTest extends PHPUnit_Framework_TestCase
{
private $pdo;
private $table;

public function testAddsBooksToTheDb()
{
$this->table->addBook('Hitchhikers guide to the galaxy');
$this->table->addBook('Robots of dawn');
$this->assertEquals(2, count($this->table->getBooks()));
}

public function testDeletesBooksFromTheDb()
{
$this->pdo->exec('INSERT INTO books (title) VALUES ("Fahrenheit 451")');
$this->table->deleteBook('Fahrenheit 451');
$this->assertEquals(0, count($this->table->getBooks()));
}

/**
* Creating a connection and table for every different test is slow,
* but there are other specific patterns to manage database fixtures.
* This pattern is about managing a fresh fixture each time,
* and a database table is a classic example of fixture.
*/
public function setUp()
{
$this->pdo = new PDO('sqlite::memory:');
$this->pdo->exec('CREATE TABLE books (title VARCHAR(255) NOT NULL, price DECIMAL(7, 2), PRIMARY KEY(title))');
$this->table = new BookTable($this->pdo);
}
}

Conclusion

If an object or resource is necessary for running the current test (like the permissions in our example when you're using a non-super user), it should be indeed created. If it is not needed, it can be safely thrown away. A final case is when the object is needed but does not help to understand the purpose of the test: in this case it can be hidden in a creation method, or the reason why it is mandatory can be investigated to find a way to isolate the System Under Test from this noise.

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.)