Test dependencies in PHPUnit 3.4

PHPUnit has become the de-facto standard for unit testing PHP code. Now in version 3.4, it has added many new and interesting features to its repertoire.

Dependencies in PHPUnit tests

PHPUnit 3.4 now supports dependencies between different test methods. It allows you to execute a particular test ONLY IF the test that it depends on executes successfully. Take the following example (Listing 1.) where we test a linked-list class I developed earlier.

A simple unit-test with PHPUnit

The test basically creates a link-list with 100 nodes, and tests a few class methods – reverseList, deleteFirstNode, deleteLastNode. As you can see the linklist creation part and method tests are combined in the single test method testLinkList().

The primary focus here is on testing the three class functions. There is nothing wrong with this. But what if the linklist creation part itself fails before the other tests are conducted? Of course we can add another test to check that the links are being created correctly, but that mixes up the two parts.

Listing 1.

<?php
 
require_once 'linklist.class.php';
require_once 'PHPUnit/Framework.php';
 
class LinkListTest extends PHPUnit_Framework_TestCase
{
    /* How many nodes to create */
    private $_nNodes = 100;
 
    public function testLinkList()
    {
        /* Linklist creation block */
        $theList = new LinkList();
 
        for($i=1; $i <= $this->_nNodes; $i++)  
        {
            $theList->insertLast($i);
        }
 
 
        /* 
            Linklist methods testing block :
 
            $theList->totalNodes() is a public function 
            in the 'linklist' class that returns the total
            number of nodes in the link-list.
        */
 
        $theList->reverseList();
        $this->assertEquals($this->_nNodes, $theList->totalNodes());
 
        $theList->deleteFirstNode();
        $this->assertEquals($this->_nNodes - 1, $theList->totalNodes());
 
        $theList->deleteLastNode();
        $this->assertEquals($this->_nNodes - 2, $theList->totalNodes());
    }
}
 
?>

Splitting up tests

A better way is to split the linklist creation part and class method testing part into two separate test functions as below (Listing 2). Here the linklist is created in the ‘testCreateList()’ method and the testing of the class methods is done in the ‘testLinkList()’ method. This allows us to keep the two testing blocks separate.

Listing 2.

<?php
 
require_once 'linklist.class.php';
require_once 'PHPUnit/Framework.php';
 
class LinkListTest extends PHPUnit_Framework_TestCase
{
    /* How many nodes to create */
    private $_nNodes = 100;
 
    public function testCreateList()
    {
        $theList = new LinkList();
 
        for($i=1; $i <= $this->_nNodes; $i++)
        {
            $theList->insertLast($i);
        }
        $this->assertTrue(!empty($theList));
 
        return $theList;
    }
 
    /**
     * @depends testCreateList
     */
    public function testLinkList($theList)
    {
        $theList->reverseList();
        $this->assertEquals($this->_nNodes, $theList->totalNodes());
 
        $theList->deleteFirstNode();
        $this->assertEquals($this->_nNodes - 1, $theList->totalNodes());
 
        $theList->deleteLastNode();
        $this->assertEquals($this->_nNodes - 2, $theList->totalNodes());
    }
}
 
?>

The important point to note here is that we have specified that the ‘testLinkList‘ method depends on the ‘testCreateList‘ method, with a @depends annotation. This tells PHPUnit that if the ‘testCreateList‘ test fails then the test dependent on it, ‘testLinkList‘, will have to be skipped, which is a logical way to go.

/**
 * @depends testCreateList
 */

Producers & Consumers in a Test

First some definitions:

a. A producer is a test method that yields its unit under test as return value.
b. A consumer is a test method that depends on one or more producers and their return values.

What it means is that a ‘Producer’ is a test method that returns some data that will be used by a ‘Consumer’ test method. In our example the ‘testCreateList‘ is a Producer and the ‘testLinkList‘ is the Consumer. The ‘testCreateList‘ method returns a linklist object which is then received by the dependent method ‘testLinkList‘. So if for some reason the ‘testCreateList‘ fails to return some data then the test that depends on it, ‘testLinkList‘, will have to be skipped, as it has nothing to work with.

Bear in mind that it does not mean that every test method that depends on some other method has to accept some arguments from the method it is dependent on. Lets breakup the above example further (Listing 3). Here we have added one more test method, ‘testNodesDefined‘, which makes sure that the ‘$_nNodes’ variable is not null. The ‘testCreateList‘ method is dependent on this method, so that if the ‘$_nNodes’ is null then the ‘testCreateList‘ is skipped, which causes the test ‘testLinkList‘ to be skipped.

Listing 3.

<?php
 
require_once 'linklist.class.php';
require_once 'PHPUnit/Framework.php';
 
class LinkListTest extends PHPUnit_Framework_TestCase
{
    /* How many nodes to create */
    private $_nNodes = 100;
 
    public function testNodesDefined()
    {
        $this->assertNotNull($this->_nNodes);
    }
 
    /**
     * @depends testNodesDefined
     */
    public function testCreateList()
    {
        $theList = new LinkList();
 
        for($i=1; $i <= $this->_nNodes; $i++)
        {
            $theList->insertLast($i);
        }
        $this->assertTrue(!empty($theList));
 
        return $theList;
    }
 
    /**
     * @depends testCreateList
     */
    public function testLinkList($theList)
    {
        $theList->reverseList();
        $this->assertEquals($this->_nNodes, $theList->totalNodes());
 
        $theList->deleteFirstNode();
        $this->assertEquals($this->_nNodes - 1, $theList->totalNodes());
 
        $theList->deleteLastNode();
        $this->assertEquals($this->_nNodes - 2, $theList->totalNodes());
    }
}
 
?>

A output from a failed test, when we set ‘$_nNodes’ to null, is shown below.
Listing 4.

D:\localhost\datastructures>phpunit --verbose LinkListTest
PHPUnit 3.4.2 by Sebastian Bergmann.
 
LinkListTest
FSS
 
Time: 0 seconds
 
There was 1 failure:
 
1) LinkListTest::testNodesDefined
Failed asserting that <null> is not null.
 
D:\localhost\test\datastructures\LinkListTest.php:13
 
There were 2 skipped tests:
 
1) LinkListTest::testCreateList
This test depends on "LinkListTest::testNodesDefined" to pass.
 
 
2) LinkListTest::testLinkList
This test depends on "LinkListTest::testCreateList" to pass.
 
 
FAILURES!
Tests: 1, Assertions: 2, Failures: 1, Skipped: 2.
 
D:\localhost\test\datastructures>

Note: The tests are executed in the sequence in which you wrote them. So take care while writing dependent tests. For example, sequencing test methods as below will not work.

    public function testNodesDefined()
    {
        $this->assertNotNull($this->_nNodes);
    }
 
    /**
     * @depends testCreateList
     */
    public function testLinkList($theList)
    {
        ..
    }
 
    /**
     * @depends testNodesDefined
     */
    public function testCreateList()
    {
        ..
    }

What all of this has brought us

Testing a complex class in a single humongous test method is not wrong, but can create a confusing mess of ‘asserts’. Splitting a single test method into multiple dependent tests can help you segregate your test methods by functionality which in turn will get you to quickly localize defects in your code.

3 thoughts on “Test dependencies in PHPUnit 3.4

Comments are closed.