Saturday, September 27, 2008

Testing Requirements with Unit Tests

See Testing Requirements with Unit Tests at its new home on bradley-holt.com.

I'm currently working on a project where the client is running their own web server that I will not have direct access to. This is actually a good thing in that it is a nice way to enforce the separation of the development and integration environments from the staging and production environments. However, how do I be sure that all of the system requirements will be met so that I can address any potential problems early on in the project? Sure, I could just give them a file with phpinfo() in it and have them send me the results. However, I plan on also shipping them unit tests for the application so why not have the requirements tested through unit tests as well?

My first thought was, since the application is being built in Zend Framework, just ship them the Zend Framework unit tests. It turns out there are a lot of unit tests and they have some heavy memory requirements. So, I decided on a simpler approach. I would run unit tests that simply tested for the requirements outlined in the Zend Framework documentation. Here are the tests:

/**
* The Requirements test case.
*
* @category Requirements
* @package Requirements
* @copyright Copyright (c) 2005-2008 Found Line, Inc. (http://www.foundline.com/)
* @license http://www.foundline.com/legal/software-license/ New BSD License
*/
class Requirements extends PHPUnit_Framework_TestCase
{

public function testVersionOfPhpIs5Dot1Dot4OrLater()
{
$this->assertTrue(version_compare(PHP_VERSION, '5.1.4', '>='));
}

public function testVersionOfPhpIs5Dot2Dot3OrLater()
{
$this->assertTrue(version_compare(PHP_VERSION, '5.2.3', '>='));
}

/**
* @dataProvider requiredExtensions
* @param string $extensionName the name of the extension for which to test
*/
public function testExtensionLoaded($extensionName)
{
$this->assertTrue(extension_loaded($extensionName));
}

public static function requiredExtensions()
{
return array(
'apc' => array('apc'),
'bcmath' => array('bcmath'),
'bitset' => array('bitset'),
'ctype' => array('ctype'),
'curl' => array('curl'),
'dom' => array('dom'),
'gd' => array('gd'),
'hash' => array('hash'),
'ibm_db2' => array('ibm_db2'),
'iconv' => array('iconv'),
'interbase' => array('interbase'),
'json' => array('json'),
'libxml' => array('libxml'),
'mbstring' => array('mbstring'),
'memcache' => array('memcache'),
'mime_magic' => array('mime_magic'),
'mysqli' => array('mysqli'),
'oci8' => array('oci8'),
'pcre' => array('pcre'),
'pdo' => array('pdo'),
'pdo_mssql' => array('pdo_mssql'),
'pdo_mysql' => array('pdo_mysql'),
'pdo_oci' => array('pdo_oci'),
'pdo_pgsql' => array('pdo_pgsql'),
'pdo_sqlite' => array('pdo_sqlite'),
'posix' => array('posix'),
'reflection' => array('Reflection'),
'session' => array('session'),
'simpleXml' => array('SimpleXML'),
'soap' => array('soap'),
'spl' => array('SPL'),
'sqlite' => array('SQLite'),
'standard' => array('standard'),
'xml' => array('xml'),
'zlib' => array('zlib'),
);
}

}

Note that I've checked for both the recommended PHP version 5.2.3 and the required PHP version 5.1.4. Also, I've tested for both hard and soft dependencies for all components. This means that many of these tests could fail and my application could still be OK. I wanted as much information as possible so that I can detect any potential problems before I write too much code. As the specific requirements for this application become clearer, I will update the test to add or remove requirements. Of course, once there is real code and unit tests these requirements tests will become irrelevant. The point is to test for the presence of these requirements before I write the code.

Running these tests with the command phpunit --testdox Requirements.php (I've actually wrapped this into a bigger test suite, but that's outside the scope of this blog post) gives me the following output on one of my development machines:

PHPUnit 3.3.1 by Sebastian Bergmann.

Requirements
[x] Version of php is 5 dot 1 dot 4 or later
[x] Version of php is 5 dot 2 dot 3 or later
[x] Extension loaded with data set "apc"
[x] Extension loaded with data set "bcmath"
[x] Extension loaded with data set "bitset"
[x] Extension loaded with data set "ctype"
[x] Extension loaded with data set "curl"
[x] Extension loaded with data set "dom"
[x] Extension loaded with data set "gd"
[x] Extension loaded with data set "hash"
[ ] Extension loaded with data set "ibm_db 2"
[x] Extension loaded with data set "iconv"
[x] Extension loaded with data set "interbase"
[x] Extension loaded with data set "json"
[x] Extension loaded with data set "libxml"
[x] Extension loaded with data set "mbstring"
[x] Extension loaded with data set "memcache"
[x] Extension loaded with data set "mime_magic"
[x] Extension loaded with data set "mysqli"
[ ] Extension loaded with data set "oci 8"
[x] Extension loaded with data set "pcre"
[x] Extension loaded with data set "pdo"
[ ] Extension loaded with data set "pdo_mssql"
[x] Extension loaded with data set "pdo_mysql"
[ ] Extension loaded with data set "pdo_oci"
[ ] Extension loaded with data set "pdo_pgsql"
[x] Extension loaded with data set "pdo_sqlite"
[x] Extension loaded with data set "posix"
[x] Extension loaded with data set "reflection"
[x] Extension loaded with data set "session"
[x] Extension loaded with data set "simple xml"
[x] Extension loaded with data set "soap"
[x] Extension loaded with data set "spl"
[x] Extension loaded with data set "sqlite"
[x] Extension loaded with data set "standard"
[x] Extension loaded with data set "xml"
[x] Extension loaded with data set "zlib"


There are a couple of potential problems to be aware of. First, PHPUnit will have to be installed on the machine that this will be tested on. This should probably only be done on the staging machine, not the production machine. Second, the CLI version of PHP often uses a different php.ini file then the CGI or ISAPI version. This means that some requirements may actually be available to your web application but they will fail the test when run at the command line.

No comments: