/* PHP & MySQL Journal */
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.
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.
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()); } } ?> |
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
*/ |
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() { .. } |
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.
|
|
This site is a digital habitat of Sameer, a freelance web developer working from Pune.More
2 Responses
1
Sameer Borate’s Blog: Test dependencies in PHPUnit 3.4 | Webs Developer
December 21st, 2009 at 9:01 am
[...] his blog today Sameer Borate has posted a method for creating test dependencies in your PHPUnit tests on your application. PHPUnit 3.4 now [...]
2
Giorgio Sironi
December 22nd, 2009 at 2:22 am
Beware however that there should be not dependencies between different test cases.