Archive for June, 2016
Ask me about Unit Testing and I’ll probably say, “nice to have”. Unit tests are very nice to have … but sometimes the project schedule doesn’t include time to build the unit tests — which often take nearly as much time as writing the functional code. But when I have time, I really like good unit tests.
I’ve never found a PHP unit test framework that I like, so I roll my own. It’s evolved over time from “call functions and see if they fail” to “test all scenarios and track complete coverage”. Here’s the basic framework I use:
- All PHP files can conduct their own unit tests by simply executing them from the command line, e.g.
- ]# php Project.php
- A script will run all unit tests
- ]# ./unitTests.sh
- All PHP functions declare their usage so that coverage can be tracked
A “hello world” class of mine looks something like this:
require_once('unitTests.php'); class HelloWorld { function findWorld() { unitTestCoverage(__METHOD__); return "Earth"; } function findGalaxy() { unitTestCoverage(__METHOD__); return "Milky Way"; } } // Unit Tests doUnitTests_HelloWorld(); function doUnitTests_HelloWorld() { // Only run tests when executed from the command line if (!(php_sapi_name() == "cli")) return; // Only run tests when it's THIS file that's executed from the command line global $argv; if (pathinfo(__FILE__, PATHINFO_FILENAME) != pathinfo($argv[0],PATHINFO_FILENAME)) return; // Create coverage arrays global $__localAllCoverage, $__localCoverage; $__localAllCoverage = [ "HelloWorld::findWorld" ,"HelloWorld::findGalaxy" ]; $__localCoverage = []; echo "HelloWorld.php unit tests\n"; $msg = "findWorld test"; $hw = new HelloWorld(); if ($hw->findWorld() != 'Earth') { unitTestFailed($msg,[$hw]); } else { echo "Success $msg\n"; } $msg = "findGalaxy test"; if ($hw->findGalaxy() != 'Milky Way') { unitTestFailed($msg,[$hw]); } else { echo "Success $msg\n"; } $diff = array_diff($__localAllCoverage,$__localCoverage); if (count($diff) != 0) { unitTestFailed('Tests did not have complete coverate.',[$__localAllCoverage,$__localCoverage,$diff]); } echo "\n\033[32m Success! \033[0m \n"; exit(0); }
And the unit test functions in unitTests.php look like this:
function unitTestCoverage($__f) { if (!(php_sapi_name() == "cli")) return; // Running unit tests! global $__localCoverage; if (!in_array($__f,$__localCoverage)) { array_push($__localCoverage,$__f); echo " .. {\033[94m" . end($__localCoverage) . "\033[0m} .. "; } } function unitTestFailed($msg,$data) { foreach ($data as $d) { print_r($d); } echo "\n \033[31m FAILED $msg \033[0m \n"; exit(1); }
In my script that runs every unit test, I check for the failure exit codes:
#/bash/bin cd www/api/classes files[0]=HelloWorld.php for i in "${files[@]}" do php $i if [ $? -ne 0 ] then echo -e "\n\nFAILURE in ${i}\n" exit 1 fi done echo -e "\n\nALL TESTS PASSED!\n" exit 0
I hope you like this framework as much as I do! It has ensured full unit test coverage and cut way down on bugs that get to a test or production stage.