DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. Languages
  4. Practical PHP Testing Patterns: Test-Specific Subclass

Practical PHP Testing Patterns: Test-Specific Subclass

Giorgio Sironi user avatar by
Giorgio Sironi
·
Mar. 28, 11 · Interview
Like (0)
Save
Tweet
Share
900 Views

Join the DZone community and get the full member experience.

Join For Free

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);
}
}
PHP

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Low-Code Development: The Future of Software Development
  • 5 Steps for Getting Started in Deep Learning
  • A Deep Dive Into AIOps and MLOps
  • Multi-Tenant Architecture for a SaaS Application on AWS

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: