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

Practical PHP Testing Patterns: Generated Value

DZone 's Guide to

Practical PHP Testing Patterns: Generated Value

· Web Dev Zone ·
Free Resource

Here is a possible scenario: exact values do not influence the behavior taken in the test. For example, the name used for saving an user is not likely to get your ifs and fors on a different path. As long as it's consistent during the test, it does not matter if it's John or Jack.

Meanwhile, you have other requirements to satisfy: for example, this value may be unique in our database, or any kind of Shared Fixture.

Goals

First of all, it's boring to define all the Literal Values when there are many of them (functional or end-to-end tests). It's even more boring to read such tests, as they include many things which are actually not important and are there just to fill in the blanks.

At the same time, we cannot just use the same values for each object to avoid confusion. For instance, while debugging a test, you may get an error message saying the User foo cannot be saved. Was it the first, the second, the third? With a dump of the test data structured we can discover it.

Implementation

Usually this pattern is implemented by a Creation Method. This method consists of:

  • a generation part, which keeps counters for generating new consecutive values, or generates random ones.
  • a possible validity check, to see if the value can be used or it is duplicated or malformed. This phase may go from a query for unicity on the test database, to any other property of the value that can be checked only at runtime.
  • an ending where you return the value or use it for building the required fixture.

Issues

The main problem with this pattern is that it leads to nondeterministic tests: if a special value triggers a failure, you have to make sure the failure message is really detailed as you wouldn't be able to easily repeat the test. By dumping the values somewhere, you may write a specific test for the exposed bug: in other runs of the Generated Value tests, the behavior will change.

Variations

In a Distinct Generated Value implementation, a sequence of progressive numbers, or strings, or floats (and so on) is generated. The primitives keep track of the last value so that they can produce the next value when asked. Between one test and another, for the sake of isolation, the counter is reset. Note that databases are expert implementors of this pattern with AUTO_INCREMENT, and may generate the value for you if you're tests involve them.

In a Random Generated Value implementation, rand() or uniqid() are called to generate a value over a large space, so that the probability of collision is practically null. The random values must be shown in failure messages to allow repeatability.

Finally, in a Related Generated Value implementation, random salt is added to a Literal or Derived Value. uniqid() can be called with a prefix argument for this purpose. This variation eases debugging, as when you dump the Generated Value, you get some hints about its provenience: you can distinguish for example between ordinary user generated names and admin generated names.

Example

The sample code shows you the three variations implementation. The actual usage of the values is omitted for brevity.

<?php
class GeneratedValueTest extends PHPUnit_Framework_TestCase
{
    /**
     * Since these values are deterministic and sequential,
     * we need to keep track of the state of the current test.
     */
    private $nextCode = 1;

    public function testDistinctGeneratedValue()
    {
        $customerCode = $this->generateNewCustomerCode();
        $otherCustomerCode = $this->generateNewCustomerCode();
        $this->assertNotEquals($customerCode, $otherCustomerCode);
    }

    /**
     * Returns the current next value, then increments it.
     * @return int
     */
    private function generateNewCustomerCode()
    {
        return $this->nextCode++;
    }

    public function testRandomGeneratedValue()
    {
        $customerCode = $this->generateNewRandomCustomerCode();
        $otherCustomerCode = $this->generateNewRandomCustomerCode();
        $this->assertNotEquals($customerCode, $otherCustomerCode);
    }

    /**
     * Randomly generated values may be very large, so it's better to limit
     * the range.
     * @return int
     */
    private function generateNewRandomCustomerCode()
    {
        return rand(1, 10000);
    }

    public function testRelatedGeneratedValue()
    {
        $customerCode = $this->generateNewRelatedCustomerCode();
        $otherCustomerCode = $this->generateNewRelatedCustomerCode();
        $this->assertNotEquals($customerCode, $otherCustomerCode);
    }

    /**
     * @return string   something like customer_4de7541c5be0c
     * The second version (with rand()) will produce a wide variety of values,
     * while uniqid() results are guaranteed to be different but not by much
     * (2-3 characters).
     */
    private function generateNewRelatedCustomerCode()
    {
        return uniqid("customer_", true);
        // return "customer_" . rand(1, 10000);
    }
}
Topics:

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}