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:

  1. All PHP files can conduct their own unit tests by simply executing them from the command line, e.g.
    • ]# php Project.php
  2. A script will run all unit tests
    • ]# ./unitTests.sh
  3. 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.