Unit Testing: Advanced

I decided to hunker down and get familiar with unit and integration testing a while back, and have finally reached a point where I’m using it with production code. I started off with the base understanding that unit testing’s purpose was to test code, but wasn’t sure in what way. In this post I will give a brief overview of what I setup and helpful tidbits I picked up along the way.

Unit tests are commonly written to ensure a unit of code (ex. class or function)  work as intended. There is also integration tests, which is meant for groups of units or “black box” integration, such as a database where the interface is being tested, and we don’t care about anything beyond that.

Prerequisites

For database integration testing, there is an extra prerequisite, the mySQL database and PDO library.

  • PHP
  • PEAR
  • PHPUnit
  • mySQL
  • PDO Library
PHPUnit uses the PDO library to handle setup and teardown (cleanup) of the database, and other PDO supported databases can be used in place of mySQL. A database is not required if you are not doing database integration.

Setup

File Structure

In addition to the basic file structure described in the first installment, there are a few additional parts to the structure.

Folders that contain PHPUnit setup files are prepended with an underscore to separate those from the test files. As you will see, there are two classes folders, one with the underscore and one without. Folders with underscores are used to configure or extend the framework and do not contain any tests.

In addition to the specially named folders, there are two files used for setup: bootstrap.php and configuration.xml.

Bootstrap

The bootstrap runs before the tests, and intended to setup the global environment. This is not to be confused with setting up test specific items that are meant to be sanitized for each tests, that logic should be placed in the setup and teardown functions which will be discussed later.

For my project, I needed to alter the include path and autoload method. Setting the include path was tricky, as they are relative to wherever the unit test is being executed from, and this may vary if your tests don’t all live in a single folder level.

Additionally, my PHP classes have a different file naming convention from the norm. For this reason I added logic in the bootstrap to handle the include paths by using spl_autoload_register. The native function file_exists does not automatically check the include path, so I broke support for the normal naming convention by doing this, but have not run into a problems (yet!).

Note: When overriding __autoload, spl_autoload_register should be used instead.

To tell PHPUnit to use your bootstrap, you must use:
–bootstrap /path/to/bootstrap.php

Configration File

PHPUnit has the ability to load an XML configuration file. Settings in that configuration file are loaded into a global, which can be used to access that data.

Example:


<?xml version="1.0" encoding="UTF-8" ?>
<phpunit>
    <php>
        <var name="DB_DSN" value="mysql:dbname=dbname;host=localhost" />
        <var name="DB_USER" value="dbuser" />
        <var name="DB_PASSWD" value="dbpass" />
        <var name="DB_DBNAME" value="dbname" />
    </php>
</phpunit>

Database Setup

Tests will need a stable database environment to execute predictable tests. While there is some setup needed to get PHPUnit to work with your database, there are some built in functions to return the database back into the state before each test was run.

I used the configuration file to load my database credentials, as shown in the PHPUnit documentation.

Test Setup and Tear Down

In addition to the standard setup and teardown methods, there are additional methods to handle database connections and data cleanup before each test.

The setup/tear down process with a database is a little more complicated then that with PHP objects. Each test should begin and end with a clean slate, therefore truncating the database tables is run before anything else. The truncation is done automatically, however the system needs to know what it’s connecting to, and what it’s touching.

getSetupOperation

There is a caveat with the truncation process with at least mySQL ver 5.5.16 , which is dealing with foreign key constraints. When PHPUnit sends the command to truncate a table, MySQL will have nothing to do with it when the constraints are in place, and the request errors out. Luckily, I was able to find some code that overrides this behavior so that we can move along to the next thing.

getConnection
A method for obtaining a database connection. This can be either a new one or existing. It is here that the settings from configuration.xml are used, as hard coding credentials gets messy.

getDataSet
Now that we have our connection established, the database needs some data to test. getDataSet is intended just for this, and can be used to insert data from a source into the database.

Stubs and Mock Objects

Stubs and Mock objects are useful for testing out variations in input/output, and assuring that methods within a class are being called appropriately.

Definitions taken from PHPUnit:

Stubs – The practice of replacing an object with a test double that (optionally) returns configured return values is refered to as stubbing. You can use a stub to “replace a real component on which the SUT depends so that the test has a control point for the indirect inputs of the SUT. This allows the test to force the SUT down paths it might not otherwise execute”.

Mocking – The practice of replacing an object with a test double that verifies expectations, for instance asserting that a method has been called, is refered to as <em>mocking</em>.

Testing classes that take a parameter during __construct and then run methods on it that require mocking took a couple extra steps to test out. For example, let’s say we have this class:


class Foo {

public $myVar;

function __construct($myVar) {

$this->myVar = $myVar;

$this->doSomething();

}

function doSomething() {

// Code

}

}


function testFoo() {
    $stub = $this->getMock('Foo', null, null, true);
    $stub->expects($this->once())
        ->method('doSomething')
        ->will($this->returnValue('foo'));
    $stub->__construct('bar');
}

First, the constructor needed to be disabled, and then mocking of the method needing testing.

Another scenario I ran into was testing out an abstract. An abstract cannot be created by itself, it needs another object to use it and define the abstract functions. PHPUnit allows for the mocking of abstract classes so that "concrete methods" can be tested out.

One last note:

According to PHPUnit documentation: "finalprivate and static methods cannot be stubbed or mocked. They are ignored by PHPUnit's test double functionality and retain their original behavior."

However, there is a work around for private/protected methods and attributes.

Conclusion

So there you have it, a brief look at PHPUnit.

Questions or Comments? Leave a Reply!

This site uses Akismet to reduce spam. Learn how your comment data is processed.