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

Practical PHP Testing Patterns: Literal Value

DZone's Guide to

Practical PHP Testing Patterns: Literal Value

· 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.

Values are present everywhere in tests: the difference between production code and test code is that the first tends to become more general with time, while the second tends to get full of examples.
Managing values for assertions and fixtures creation is one of the task required in creating a test suite. There are multiple patterns that address this concern - today we will see the simplest one, Literal Value.

Implementation

Literal Values are constants that we put in place of input and expected data in tests. Usually these values are of primitive type, such as strings and integers, but the pattern extends to cover simple objects created inline.

The greatest advantage of Literal Values is that they never change: your test suite will run always with the same input data, and occasional test failure will be reproducible simply by re-running the red test. Having to repeat runs for 10 times to be statistically sure that there you have introduced no bug is a measure of last resort, while Literal Values keep the test as much deterministic as possible.

Semantics

Unfortunately Literal Values may lead to obscure tests:

$this->assertEquals(40, $order->calculateTax(100, 20, 3));

In this case making the value a bit more explicit may help:

$this->assertEquals(100 - 20 * 3, $order->calculateTax(100, 20, 3));

When I first saw this approach, I though we were introducing duplication between production code and tests. But actually with this assertion we're testing that the method automates the computation over any possible input, which is a larger step than producing a Literal Value like we do in the test. More complex generation procedures are out of the scope of this pattern.

One thing we have also to pay attention to is the usage of identical values where they are not necessary: if you write the same number in a test in two places, you communicate the intent that those two variables must always be equal. If the SUT is meant to run also with different values, you should definitely use always different values.

Reuse of values is instead beneficial throughout different Test Methods: the reader will know (hopefully) that they are isolated from each other, and a value cannot exit the last } of the method.

Variations

  • Symbolic Constant: we need to use the same value in two places, and communicate this intent. By using directly a literal we should have no misunderstandings, but we may have maintenance issues if we want to replace it and we forgot to check all places. In this case, extracting a constant or even just a member variable on $this centralizes the actual value; it also lets you give it a name (self::MY_VEHICLE instead of 1.)
  • Self-Describing Value: we use values which are only passed around, and do not influence the computation, to convey information about their role. For example, we may call vehicles which we want to delete Old banger and vehicles that must be preserved New flaming Ferrari. We may call a bad driver John Disaster and the good one Mark Rallyist. Dont' get too clever on this, although it is a great place for Easter eggs. Just avoid meaningless names like driver A, B, C and D when you're in a domain-level test where you could choose better.

Example

The code sample shows a group of assertions using Literal Values and how to avoid replicating them too much in different tests.

<?php
class LiteralValueTest extends PHPUnit_Framework_TestCase
{
    const POSITIVE = 1;
    const NEGATIVE = -1;

    public function setUp()
    {
        $this->sut = new SUT();
    }

    /**
     * The most basic of the tests.
     */
    public function testALiteralValueIsUsedForExpectationAndInput()
    {
        $this->assertEquals(5, $this->sut->sum(2, 3));
    }

    /**
     * If we change the result from 1 and -1 to "+1" or to an object,
     * we will have a way to quickly change this test. It's normal refactoring.
     */
    public function testASymbolicConstantIsExtractedToAvoidDuplicatingAValue()
    {
        $this->assertEquals(self::POSITIVE, $this->sut->sign(0));
        $this->assertEquals(self::POSITIVE, $this->sut->sign(1));
        $this->assertEquals(self::POSITIVE, $this->sut->sign(2));
        $this->assertEquals(self::POSITIVE, $this->sut->sign(10));
        $this->assertEquals(self::NEGATIVE, $this->sut->sign(-1));
        $this->assertEquals(self::NEGATIVE, $this->sut->sign(-2));
        $this->assertEquals(self::NEGATIVE, $this->sut->sign(-10));
    }
    
    /**
     * Describing cars that have to be deleted as old and rusty is simpler
     * for the mind RAM of the reader that using A, B and C. Choosing 
     * Self-Describing Values is an art that I have only started exploring.
     */
    public function testASelfDescribingValueIsUsedToMakeTheTestMoreReadable()
    {
        $this->sut->addVehicle('Old rusty car', 1980);
        $this->sut->addVehicle('Bus', 2000);
        $this->sut->addVehicle('Ferrari', 2010);
        $this->sut->deleteVehiclesUpTo(1990);
        // another Literal Value
        $this->assertEquals(2, $this->sut->getVehiclesCount());
    }
}

/**
 * Since we only want to describe the test code in this sample,
 * we use this catch-all SUT which accepts any call.
 */
class SUT
{
    public function __call($method, $arguments) {}
}

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 }}