Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Practical PHP Testing Patterns: Test Utility Method

DZone's Guide to

Practical PHP Testing Patterns: Test Utility Method

· Web Dev Zone ·
Free Resource

Jumpstart your Angular applications with Indigo.Design, a unified platform for visual design, UX prototyping, code generation, and app development.

One thing we can never forget is that we have to reduce duplication in test code: tests should have the same dignity as production code, and we should refactor them when necessary, protecting the investment in the test suite from rotting.

The basic refactoring move we have at our disposal in code (both production and test one) is the Extract Method refactoring, described by Fowler in its famous book on the subject.

When Extract Method is applied to test code, the result is a Test Utility Method: a method that encapsulates logic so that it can be reused in different Test Methods, or even different Testcase Classes.

A little background

Why were we eliminating duplication? Oh, yeah:

  • to increase the maintainability of our test code, so that when the constructor of the System Under Test changes its parameters, we have just a few lines to adapt instead of repeating the change dozens of times.
  • to improve readability: a test of 3 lines which uses Test Utility Methods is simpler to read than 10-line one, which in turn is simpler to understand than a 100-line one.

Besides this old fashioned refactoring benefits, extracting methods in tests, when done well, creates a feeling in the developers that writing new tests is simple and quick. They will be able to reuse this methods and write test at an high level of abstraction.

They will favor adding a test to expose a bug instead of blindly debugging; the tests will help them and remain to avoid regressions.

Who tests the tests?

Introducing too much logic in test code is prone to introducing also bugs. You may want to insert assertions in the Test Utility Methods to ensure they are working well.

Implementation

All the recommendations for extracting methods apply here: choose well your names: this is an Api the developers should grasp quickly.

Basically, a Test Utility Method can start its life as a private method in the current class, which may or may not use $this. When not referring to $this, parameters and return values are the tools of exchange with the rest of the test code.

When a Test Utility Method becomes duplicated in different Testcase Classes, you can share it between these classes via inheritance, by defining a common superclass. Another method for sharing code between tests is by introducing Test Helpers, which we will explore in a dedicated article.

Variations

There are many things that may become duplicate between tests, and so be extracted. The variations of this pattern have different goals, and we have already seen many of them:

  • Creation Method: creates part of the fixtures, or something that is needed prior to running the test.
  • Attachment Method: a specialization of Creation Method which includes an already existing fixture into the SUT, or in the current test.
  • Finder Method: encapsulates the logic to retrieve a Shared Fixture (like a database connection with a ready-to-use schema).
  • SUT Encapsulation Method: encapsulates unnecessary knowledge of the Api of the SUT, such as earlier method calls, repeated creation, and complex signatures.
  • Custom Assertion: easy to explain.
  • Verification Method: hides the verification phase, by keeping together multiple assertions but also the interaction with the SUT (act and assert phases).
  • Parameterized Test: a Test Method that can be run multiple times, with different input/expected output data. PHPUnit provides the @dataProvider annotation for implementing this variation with very little code.
  • Cleanup Method: extracts the teardown code.

Example

The example shoes you the difference between a Testcase Class and its refactored version, containing many Test Utility Methods.

<?php
class TestUtilityMethodBeforeTest extends PHPUnit_Framework_TestCase
{
public function testIsCountable()
{
$array = new ArrayObject();
$n = 6;
for ($i = 0; $i < $n; $i++) {
$array[] = 'dummy';
}
$this->assertEquals($n, count($array));
}

public function testAfterCreationContainsNoElements()
{
$array = new ArrayObject();
$this->assertEquals(0, count($array));
}

public function testAfterAdditionContainsTheElement()
{
$array = new ArrayObject();
$array[] = 'foo';
$this->assertEquals(1, count($array));
$index = array_search('foo', $array->getArrayCopy());
$this->assertSame(0, $index);
}

public function testSortsItselfAccordingToValues()
{
$array = new ArrayObject(array('v' => 'value', 'f' => 'foo'));
$array->natsort();
$this->assertEquals('foo', $array->getIterator()->current());
$index = array_search('foo', $array->getArrayCopy());
$this->assertSame('f', $index);
$index = array_search('value', $array->getArrayCopy());
$this->assertSame('v', $index);
}

public function testAfterRemovalContainsNoElements()
{
$array = new ArrayObject(array('foo'));
unset($array[0]);
$this->assertEquals(0, count($array));
}
}

class TestUtilityMethodAfterTest extends PHPUnit_Framework_TestCase
{
public function testIsCountable()
{
$array = new ArrayObject();
$this->fillWithValues($array, 6);
$this->assertCountIs(6, $array);
}

public function testAfterCreationContainsNoElements()
{
$array = new ArrayObject();
$this->assertIsEmpty($array);
}

public function testAfterAdditionContainsTheElement()
{
$array = new ArrayObject();
$array[] = 'foo';
$this->assertCountIs(1, $array);
$this->assertKeyIs(0, 'foo', $array);
}

public function testSortsItselfAccordingToValues()
{
$array = new ArrayObject(array('v' => 'value', 'f' => 'foo'));
$array->natsort();
$this->assertEquals('foo', $array->getIterator()->current());
$this->assertKeyIs('f', 'foo', $array);
$this->assertKeyIs('v', 'value', $array);
}

public function testAfterRemovalContainsNoElements()
{
$array = new ArrayObject(array('foo'));
unset($array[0]);
$this->assertIsEmpty($array);
}

/**
* Sort of Creation Method; although it does not instance the object,
* it prepares it for the test.
*/
private function fillWithValues(ArrayObject $array, $howMany)
{
for ($i = 0; $i < $howMany; $i++) {
$array[] = 'dummy';
}
}

/**
* Three Assertion Methods.
*/
private function assertIsEmpty(Countable $array)
{
$this->assertCountIs(0, $array);
}

private function assertCountIs($number, Countable $array)
{
$actual = count($array);
$this->assertEquals($number, $actual, "The count is not $number but $actual.");
}

private function assertKeyIs($expectedKey, $value, ArrayObject $array)
{
$key = $this->keyOf($value, $array);
$this->assertSame($expectedKey, $key);
}

/**
* A simple Test Utility Method to give some meaningful name
* to array_search()
*/
private function keyOf($value, ArrayObject $array)
{
return array_search($value, $array->getArrayCopy());
}
}

Take a look at an Indigo.Design sample application to learn more about how apps are created with design to code software.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}