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: Test Double

03.02.2011
| 5040 views |
  • submit to reddit

The Test Double pattern is the key to testing in isolation: not only isolation from other objects, but also from the network, the file system, the database, time(), rand() and everything else you're going to normally integrate in your application.

The idea of the Test Double pattern is that instead of testing a whole object graph, we substitute ordinary collaborators of the SUT with some doubles (like actors body doubles). At first producing code which will only be used by tests seems wasteful, but actually Test Doubles are very quick to write and provide immense value by isolating the class[es] under test.

The SUT is thus reduced to a smaller object graph, or to even a single real object which works with other simplicistic, easy to test objects. In practice, Test Doubles are alternate implementations of the contract the SUT requires its collaborators to adhere to: they have the same interface or class of the real collaborators, which you will use in production.

Why I can't use a Test Double?

The design of the production code must accomodate for the use of Test Doubles: like Misko says, there is no testing magic that you can add afterwards, but only a responsibility from the developers to write testable code. Dependency Injection, or at least a Service Locator as another form of DIP is needed for being able to substitute production code objects with Test Doubles as long as you are in the test environment.

You cannot use test doubles if the code under test uses the new operator for the production classes: so don't use the new operator for objects you want to substitute, but inject them from the outside. You cannot use test doubles if the code calls static classes or singletons: so don't use static classes and singletons if you want to test your code in isolation from them. Use them if you do not mind pulling them in your test.

I won't go into detail in design for testability since it is a wide topic and I have covered in the past. Here are some resources if you are interested in the topic.

 

Test Doubles specializations

Test Doubles are not one-size-fits-all - there are many variations of the pattern which will be covered in the next article of this series.
  • a Dummy is an object which is simply pass around to satisfy method signatures and checks. It's the simplest Test Double you can build.
  • a Stub provides canned results when called.
  • a Spy records calls and parameters so that you can check them afterwards, inside the test.
  • a Mock is similar to a Spy, but checks calls immediately according to a contract you have defined earlier. It can also provide canned results.
  • a Fake is a simpler-to-use full implementation of a real object, like a in-memory object that substitute a DAO.

Implementation

You have several design choices to make when implementing any Test Double.

Whether the object would be specific to this test or reusable. The differences is that specific Test Doubles are usually hardcoded and very simple; reusable tend to incorporate a little of logic for matching parameters and state to an output.

Whether the class has to be coded manually or be generated. PHPUnit provides getMock() for generation of Test Double classes for abstract and concrete classes, and also interfaces. The differences between the two approaches are that coding manually is very clear, versatile and portable, but can be tedious especially with the absence of private classes in PHP. On-the-fly code generation a bit less flexible but scales better. Note that in PHPUnit the generated code is never written on disk, and it is regenerated at every new process (so no need to keep it in sync with class definition; abstractions like a Mock's interface should be stable but it's not always the case especially if you are currently working on it...)

How to wire the SUT to the test double; via injection, configuration, magic... Injection is my preferred choice, but consider also injection of factories or configuration of class name and parameters (like Zend Framework does with application resources.)

Example

I didn't want to leave you without some code today, so here is an example of a Test Double, in this case a Stub. We will cover each of the variations with code in the next articles.
<?php
class TestDoubleTest extends PHPUnit_Framework_TestCase
{
public function testCalculatesAverageVisitorsNumber()
{
$source = new StubDataSource(array(40000, 50000, 100000, 20000, 40000));
$statistics = new Statistics($source);
$this->assertEquals(50000, $statistics->getAverage());
}
}

/**
* The System Under Test. It requires a DataSource collaborator to be used
* in production, or to be tested.
*/
class Statistics
{
private $source;

public function __construct(DataSource $source)
{
$this->source = $source;
}

public function getAverage()
{
$samples = $this->source->getSamples();
return array_sum($samples) / count($samples);
}
}

/**
* This is the contract of the source, the collaborator for the SUT.
* It's not mandatory to have an explicit interface, particularly in PHP,
* but it helps.
*/
interface DataSource
{
/**
* @return array numerical values of visitors to this website
*/
public function getSamples();
}

/**
* Unfortunately, the real implementation of DataSource talks to an external
* web service. Even if we reconfigure it, it will still involve using the
* network (no testing when offline, and slower testing when online with
* respect to in-memory tests); it also probably gives different results
* depending on the current date, and we would have to update our assertions.
*/
class GoogleAnalyticsDataSource implements DataSource
{
public function getSamples()
{
$result = curl("...");
return $result;
}
}

/**
* An alternate implementation of DataSource, which is simply configured with
* predefined results. It returns them every time it is called, without
* being affected by any global or external state.
*/
class StubDataSource implements DataSource
{
private $samples;

public function __construct(array $samples)
{
$this->samples = $samples;
}

public function getSamples()
{
return $this->samples;
}
}
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.)