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-Specific Subclass

03.28.2011
| 4464 views |
  • submit to reddit

All objects have some private state: usually well-encapsulated, and not exposed with a bunch of getters and setters. But for making verifications, if the SUT was not thought with testing in mind, you have often to access this private state.

There is a really specific testing pattern (in the sense that it does not contribute to your design, like Dependency Injection, but only to the test suite) that you can use in these cases: Test-Specific Subclass.

Beware the goblins

Accessing the private state of an object ties the tests to implementation details, instead of treating SUT as black box. Tests will become more difficult to maintain.
Consider also alternate solutions, if you're not dealing with legacy code:

  • changing the implementation to make it more testable: favor composition and Dependency injection to keep the private members of an object onto another collaborator.
  • avoid testing state which is really implementation details and not part of the observable state of the system; only verify the end results.

Implementation

Beware again that I usually do this is on legacy code.

There are various tricks for accessing private (really protected, only) members of a class, and they involve preparing a subclass which redefines them and delegate to the originals.

You can for example add getters and setters to expose a protected field. Or you can add methods that expose the fields in a test-friendly way, or manipulate them in ways that were not predicted by the System Under Test.

You can also override methods and enlarge their visibility, delegating back to the parent ones.

This pattern influences the SUT to a certain extent, in two qualitative metrics:

  • feature granularity: methods to be overridden must be extracted in the original class, and not just remain inlined code. This is positive as it's one step towards composition.
  • Feature visibility: members to access from the subclass must be protected and not private. This is negative as you reduce encapsulation for the sake of testing.

Abstract classes

A corner case of Test Specific Subclass is the one of abstract classes: you can't instance an abstract class for testing, so as an hack you prepare the simplest concrete subclass that compiles (defines all abstract methods) and test that.

For example, the Template Method and Factory Method patterns are commonly tested this way.

Consider that if you extracted an abstract class in a Test-Driven Development process, this means there is already a concrete class which is tested. So you don't need to test the abstract class independently.

Variations

Variations of the pattern expose different parts of the original class.

  • State-Exposing Subclasses show the internal state of the SUT, via accessors and possibly mutators.
  • Behavior-Exposing Subclasses expose as public some internal methods, so that they can be tested independently with briefer tests than the ones involving also the public Api.
  • Behavior-Modifying Subclasses overridde some methods to simplify the test scenario, for example substituing a complex hook method with one that does nothing.
  • Test Doubles are really test-specific subclasses sometimes, but they are automatically maintained if you use code generation. Note that if you mock interfaces, they are not Test Double Subclasses; and mocking concrete classes is considered bad as it indicates a lack of an abstraction, but can be done in emergency situation and to temporarily tame legacy code.
  • Substituted Singleton: I don't even know where to start for telling you how problematic this pattern is. Just skip to the Dependency Injection page of the manual.

Examples

The code sample shows you how a Behavior-Exposing Subclass is commonly implemented, and also rewrites from scratch the same functionality by using instead composition.

<?php
class TestSpecificSubclassTest extends PHPUnit_Framework_TestCase
{
/**
* MoneyFund is our SUT, and this is the test for its public Api.
*/
public function testMoneyFundMainFeature()
{
$moneyFund = new MoneyFund(100000, 4);
$moneyFund->afterYears(1);
$this->assertEquals(104000, $moneyFund->getCapital());
}

/**
* We can test internal parts of MoneyFund via a Test Specific Subclass.
*/
public function testPrivateMethodOfMoneyFund()
{
$moneyFund = new TestSpecificMoneyFund(100000, 4);
$this->assertEquals(4000, $moneyFund->calculateInterests());
}

/**
* By the way, an alternate design consider the internal part another
* object. Here the SUT is isolated from the new collaborator.
*/
public function testCompositionMoneyFundMainFeature()
{
$interestCalculator = $this->getMock('InterestCalculator');
$interestCalculator->expects($this->once())
->method('calculateInterests')
->with(100000)
->will($this->returnValue(4000));
$moneyFund = new CompositionMoneyFund(100000, $interestCalculator);
$moneyFund->afterYears(1);
$this->assertEquals(104000, $moneyFund->getCapital());
}

/**
* As a result, we can test what was previously just internal functionality
* without Test Specific Subclasses, but just with other public methods.
*/
public function testPublicMethodOfInterestCalculator()
{
$calculator = new InterestCalculator(4);
$this->assertEquals(4000, $calculator->calculateInterests(100000));
}
}

/**
* The original SUT.
*/
class MoneyFund
{
public function __construct($capital, $interestRate)
{
$this->capital = $capital;
$this->interestRate = $interestRate;
}

public function afterYears($years)
{
for ($i = 1; $i <= $years; $i++) {
$this->capital += $this->calculateInterests();
}
}

/**
* Internal method we want to test.
*/
protected function calculateInterests()
{
return round($this->capital * $this->interestRate / 100, 2);
}

public function getCapital()
{
return $this->capital;
}
}

/**
* Test Specific Subclass.
*/
class TestSpecificMoneyFund extends MoneyFund
{
public function calculateInterests()
{
return parent::calculateInterests();
}
}

/**
* The refactored SUT.
*/
class CompositionMoneyFund
{
public function __construct($capital, InterestCalculator $calculator)
{
$this->capital = $capital;
$this->interestCalculator = $calculator;
}

public function afterYears($years)
{
for ($i = 1; $i <= $years; $i++) {
$this->capital += $this->interestCalculator->calculateInterests($this->capital);
}
}

public function getCapital()
{
return $this->capital;
}
}

/**
* The collaborator modelling the internal functionality.
*/
class InterestCalculator
{
private $rate;

public function __construct($rate = 0)
{
$this->rate = $rate;
}

public function calculateInterests($capital)
{
return round($this->rate / 100 * $capital, 2);
}
}
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.)