Practical PHP Testing Patterns: Suite Fixture Setup
If you have a Shared Fixture you want your tests to use, there is an alternative to implementing it as a Prebuilt Fixture which is built during bootstrap. This solution ties a little more into the testing framework, but will prevent a fixture from being instanced when not necessary.
The goal of this fixture creation pattern is the same as of Prebuilt Fixture however: build a Shared Fixture before even the first test is run, so that the test code itself is freed of the burden, and does not have to repeat this boring setup in each different scenario. This boring setup may be a sequence of new statements, but also a call to a Creation Method.
Implementation in PHPUnit
The --bootstrap hook of PHPUnit can be considered an hybrid between Prebuilt Fixture and this pattern, Suite Fixture Setup. The reason is that PHPUnit does not provide hooks for a really pure version of these patterns:
- a pure Prebuilt Fixture should be ready before the PHPUnit executable starts. For instance, we would have a Phing or Ant target that executes fixture building and then calls PHPUnit. I used that to build a fresh schema of the database for my tests, and use that one as a Shared Fixture (much faster than recreating it every time).
- a pure Suite Fixture Setup requires programmatic hooks (the technical term for a method to override and insert code into) in the Test Suite object. The reference class is PHPUnit_Framework_TestSuite, but usually PHPUnit instantiates this class by itself, and let the user generate test suites on the fly via --filter or --group options, by picking only some Testcase Classes or test methods.
It is possible to use the famous AllTests files (but that's not strictly necessary) and an extension of the base Test Suite class to run setUp() and tearDown() method on a suite-wide basis, and I will show you how. However, the problem with this kind of suites is that they have to be maintained, usually by harcoding the list of Testcase Classes in them. If you have an explictly defined Test Suite, you have additional setUp() and tearDown() hooks on the subclass which are run one time per suite; however you lose autodiscovery as every new Testcase class in that suite must be added manually.
Once you have make an habit of test autodiscovery, it's hard to go back. In the last year, I think I have not run a single AllTests file for production code: I simply name my test files with a Test.php suffix and they're automatically gathered for running by PHPUnit. However, test autodiscovery works also with Test Suite objects, albeit with some tricks.
Enough of the theory, let's see some code.
First, we can define two tests.
This is MyTestInSuite.php:
class MyTestInSuite extends PHPUnit_Framework_TestCase
public function testSomething()
While this is YourTestInSuite.php:
class YourTestInSuite extends PHPUnit_Framework_TestCase
public function testSomethingElse()
Notice that I have not given them the Test.php suffix, because I do not want to run them one at the time. There are some echo statements in order to understand what is going on later by looking at the output of various commands.
Now I build a Test Suite class:
class ASimpleSuiteTest extends PHPUnit_Framework_TestSuite
public function setUp()
public static function suite()
$suite = new self();
The source files of the composed Testcase Classes must be included manually, and listed in the suite() method. This Test Suite file has the Test.php suffix in the name, since it is the one that PHPUnit should autodiscover.
Now we can run the whole Test Suite object, and execute a single setUp():
[10:29:59][giorgio@Desmond:~/txt/articles/suitefixturesetup]$ phpunit .
PHPUnit 3.5.5 by Sebastian Bergmann.
Time: 1 second, Memory: 5.00Mb
OK (2 tests, 2 assertions)
Occasionally, we may want instead to run only particular tests, but always by running the suite's setUp() first. We can do so by filteringthe test method's name:
[10:33:23][giorgio@Desmond:~/txt/articles/suitefixturesetup]$ phpunit --filter testSomethingElse .
PHPUnit 3.5.5 by Sebastian Bergmann.
Time: 0 seconds, Memory: 5.00Mb
OK (1 test, 1 assertion)