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 636 posts at DZone. You can read more from them at their website. View Full User Profile

Practical PHP Testing Patterns: Standard Fixture

11.08.2010
| 6029 views |
  • submit to reddit

We have entered the realm of fixtures, a term that encompasses all the setup necessary prior to running a test, from instantiating the System Under Test to preparing the input data for it.

The Standard Fixture pattern is applied when you attempt to reuse the fixture setup logic. Note that this does not mean necessarily reuse the fixture itself: data, objects, or database rows can be recreated in each test so that test isolation is maintained. The shared part, however, must comprehend code.

A Standard Fixture usually is shared at the level of the test case, but can be also used in multiple ones when kept on an external object like a Test Helper. This pattern is in contrast with the Minimal Fixture one, where each test has its own custom fixture.

Advantages

There is mainly one benefit or reusing a fixture in each test: it is a less effort solution, since you write it one time instead of N times, whereas N is the number of different test methods. setUp() and tearDown() methods in xUnit frameworks like PHPUnit even allow you to insert the fixture creation code in them, and leverage these hooks so that the test become shorter and easier to read.

For example, moving the creation of the SUT into a setUp() method is quite common, although some TDDers have the right to say that it makes the test less readable as it hides the starting state of the system into another method, which is not called directly.

Disadvantages

There are however some issues in just using a Standard Fixture:

  • catch-all fixtures are sometimes difficult or artificial to write. It's not always possible or desiderable to share a common setup for tests, since for each of them certain bits of the fixture would be unused and considered noise. For example, setting up the same database rows for each functional test is a Standard Fixtures, and such a solution does not tell you which rows are interested by each test.
  • It may also be the case that the SUT acts on part of the fixture that it is not intended to touch or rely on. You cannot realiably test that your UserDAO does not touch the 'groups' table if you always insert data in it before each test as a Standard Fixture.
  • A Standard Fixture may also become fragile when extended to more and more tests: after a certain point, you cannot introduce a new test with the same fixture because adapting the fixture will probably break the earlier written tests. Conflicting fixtures for different tests play a part in this scenario.
  • This kind of fixtures are also slower to setup, because they are larger than each of the Minimal Fixtures which would be designed for single tests.

Implementation

Standard Fixtures are usually written in advance, so that the tests can be designed to use this common fixture.

An important part of the pattern's definition is that the lifetime of the fixture created (one test, or more than one test) is not defined. This distinction between Shared and Fresh Fixtures will be addressed by the next patterns in this series.

Example

The code sample tries to test two PHP native functions with a Standard Fixture. The resulting code is very concise, but I'll somehow feel a lack of clarity there. If the number of test methods grows, I'll indeed create some Minimal Fixtures to avoid overloading the Standard one.

<?php
class MultidimensionalArrayTest extends PHPUnit_Framework_TestCase
{
    /**
     * The two test methods exercise two native functions of PHP
     * which work on multidimensional arrays. The array which they
     * operate on is a Standard Fixture, since in the first test 
     * it is equal to the other one.
     */
    public function testArrayMergeRecursive()
    {
        $startingArray = $this->createMultidimensionalArray();
        $result = array_merge_recursive($startingArray, array(
            'key'      => array(100),
            'otherKey' => array(1000, 10000)
        ));
        $this->assertEquals(array(
            'key'      => array(4, 8, 15, 100),
            'otherKey' => array(16, 23, 42, 1000, 10000)
        ), $result);
    }

    public function testArrayWalkRecursive()
    {
        $array = $this->createMultidimensionalArray();
        array_walk_recursive($array, function(&$value) {
            $value += 10;
        });
        $this->assertEquals(array(
            'key' => array(14, 18, 25),
            'otherKey' => array(26, 33, 52)
        ), $array);
    }

    /**
     * This private method creates the Standard Fixture.
     */
    private function createMultidimensionalArray()
    {
        return array(
            'key'      => array(4, 8, 15),
            'otherKey' => array(16, 23, 42)
        );
    }
}

Conclusion

Meszaros's book chapter on Standard Fixture tells us that agilists favor the Minimal Fixture pattern, since they write one test at the time and each of them requires a different fixture to be written. Indeed I am an XPer and practice Test-Driven Development, and I wrote the Minimal Fixture article naturally.

Quality assurance testers prefer instead Standard Fixture written in advance, since they interact with an fully built system and this pattern can simplify their work.

My advice is usually to start with Standard Fixtures for your tests, to avoid working too much on Minimal Fixtures, but as soon as you notice one of the disadvantages like slowness or unclarity, redesign the tests with Minimal Fixtures. At the unit level however, you'll discover that if the design is robust, fixtures will be easy and short to write: in this case, Minimal Fixtures won't be an hassle but an asset that transforms your tests in readable documentation.

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