Over a million developers have joined DZone.
Platinum Partner

Practical PHP Testing Patterns: Test Method

· Web Dev Zone

The Web Dev Zone is brought to you by Stormpath—offering a pre-built Identity API for developers. Easily build powerful user management, authentication, and authorization into your web and mobile applications. Download this Forrester report on the new landscape of Customer Identity and Access Management.

We are now starting the xUnit Basics Patterns part of this series. We will move on from the general ideas of testing strategy to more mundane things, like the organization of code in test methods and their internal structure.

The Test Method pattern is applied when you encode each test as a single Test Method on a class. Each test is standalone: it someway boostraps the fixtures it needs, like an object or a resource, then it exercises a part of it.

Implementation

A method, particularly in PHP, has a natural isolation of scope which we can't easily find with other constructs (code blocks for example do not have scope isolation in PHP.) Once we exit the method at the end of the test, the variables contained are automatically discarded.

Once we have defines our Test Methods, a test runner will use reflection to gather all the method names and run them, one at the time. PHPUnit instances also a different object of the class for each different method, to provide isolation not only for the local variables but also for the member ones.

Usually only some methods are run: the ones marked for tests. In PHPUnit, which implements the Test Method pattern as all the xUnit frameworks do, the methods starting with the word test are understood as test methods. You can define additional helper methods simply by naming them without this prefix.

If you regard tests as a low-level specification and documentation, the Test Method names should be descriptive: tests for an ArrayObject may be named testAddsAnObjectToTheCollection, testRetrievesAnObjectBasingOnOffset. The convention followed in Agile circles is to start them with as third-person singular verb, where the subject of the sentence is the System Under Test.

Variations

This pattern is specialized into different categories of Test Methods. Here are the one cited in xUnit testing patterns.

Simple Success Test

This kind of test exercises a best path, without taking into account any failures. We make assertions on the result of the called methods to check their correctness. Usually, the first test that has to be written is a Simple Success Test.

This type of tests does not comprehend catching of exceptions or error management.

Expected Exception Test

In this tests, we do something wrong on purpose, and see that an exception is raised which satisfy our expectations. Exceptions are the standard error management tool in object-oriented programming.

PHPUnit offers @expectedException annotation, along with the @expectedExceptionCode and @expectedExceptionMessage if checking the exception class or interface is not enough.

There are no happy paths exercised by these tests: each of them must provoke an error, or PHPUnit would tell you that it expected exception XXXException but it wasn't raised during all the test method run.

Since the catching is done internally by PHPUnit, you do not need to insert try/catch blocks, except for particular cases where you want to be more precise on the origin of the thrown exception. In this case, you may insert try/catch block on part of the method to check that a particular method call raises the exception, and not any other line of code. You can also call $this->setExpectedException() just before provoking the error.

Constructor test

These tests exercise a constructor, and in general the initial state of an object, separately from the Simple Success Test. In these tests, you instance an object, and then start making assertions on its initial state.

However, if the constructor makes too much work, this would make the other tests brittle as they call the constructor themselves (it can't be stubbed out.) A general practice is to insert assertions after creation for clarity (checking that a crucial field is null or 1 or equal to some other constant), but not doing actual work in the constructor, leaving the wiring code to a creational pattern such as a Factory or a Builder and freeing the other tests from the burden of depending on construction code.

Example

The code sample shows three tests, one for each variation, that exercise an ArrayObject. I always choose native classes to simplify the examples, that can simple show how testing patterns work instead of forcing the user also to learn a particular throwaway SUT.

<?php

class ArrayObjectTest extends PHPUnit_Framework_TestCase
{
    /**
     * Construction Test. It is logical to keep such tests at the start
     * of a Test Case, for clarity.
     */
    public function testIsEmptyAtConstruction()
    {
        $this->assertEquals(0, count(new ArrayObject()));
    }

    /**
     * Simple Success Test. These tests are the bread and butter of a
     * Test Case.
     */
    public function testInsertsAValueInTheCollection()
    {
        $arrayObject = new ArrayObject();
        $arrayObject['key'] = 'value';
        $this->assertEquals('value', $arrayObject->offsetGet('key'));
    }

    /**
     * Expected Exception Test. Testing error conditions is important
     * both for debugging purposes and for handling errors gracefully
     * instead of dysplaying a blank page.
     * @expectedException PHPUnit_Framework_Error_Warning
     * @expectedExceptionMessage Illegal offset type
     */
    public function testDoesNotAllowForANonScalarKeyToBeUsed()
    {
        $arrayObject = new ArrayObject();
        $key = new stdClass;
        $arrayObject[$key] = 'value';
    }

    public function testDoesNotAllowForANonScalarKeyToBeUsed_Alternative()
    {
        $arrayObject = new ArrayObject();
        $key = new stdClass;
        $this->setExpectedException('PHPUnit_Framework_Error_Warning');
        $arrayObject[$key] = 'value';
    }
}

The Web Dev Zone is brought to you by Stormpath—offering a pre-built, streamlined User Management API for building web and mobile applications. Check out our top pointers for designing your REST API.

Topics:

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

{{ parent.tldr }}

{{ parent.urlSource.name }}