Unit testing is a very powerful form of debugging which many developers don’t know about. Once your applications get bigger, it is daunting to make any changes in fear of breaking the entire application. Re-testing the entire application as a whole is both silly and time consuming. Even having to test various components of the application as a user is silly. If all the pieces work, the application will work!

Code should be broken down into small enough chunks (well written OOP) that it can all be executed separately. The idea behind unit testing is programmatically testing all the pieces of your application individually and independently of the next.

We not only want to test how something should work, but also try to break it. If we throw both acceptable and unacceptable values at a function, we can work on handling those error cases, making certain that it will work no matter what is thrown at it, resulting a stricter, clearer application.

In this post we will use SimpleTest, a unit testing framework for PHP.

Don’t worry too much about the following code — I will not go deeply into SimpleTest’s internals as this is more of an introduction than a tutorial. Here is my main test initiation file:

Code: (Plain Text)
  1. <?php
  2.  
  3. error_reporting(E_ALL & ~E_NOTICE);
  4.  
  5. define(‘THIS_SCRIPT’, ’simpletest’);
  6. define(‘SIMPLE_TEST’, ‘c:\www\simpletest’);
  7. define(‘TEST_DIR’,    ‘c:\www\scripts\tests’);
  8. define(‘DISABLE_HOOKS’, 1);
  9.  
  10. $globaltemplates  = array();
  11. $phrasegroups     = array();
  12. $actiontemplates  = array();
  13. $specialtemplates = array();
  14.  
  15. require(‘./global.php’);
  16.  
  17. require_once(SIMPLE_TEST . ‘/unit_tester.php’);
  18. require_once(SIMPLE_TEST . ‘/reporter.php’);
  19. require_once(SIMPLE_TEST . ‘/mock_objects.php’);
  20.  
  21. $reporter = @$_SERVER[‘argv’][1] === ‘commandline’ ? ‘TextReporter’ : ‘HtmlReporter’;
  22.  
  23. $test = new GroupTest(‘All tests’);
  24. $test->addTestFile(TEST_DIR . ‘/array.php’);   
  25. $test->run(new $reporter());
  26.  
  27. ?>

For each file (containing classes) you wish to test, just call $test->addTestFile($file) again and it will be added to the list.

In my example I will deal with testing various array functions, since it’s quite an easy thing to visualize, and may also act as a guide for the actual functions since some may be confusing. Before that, here is a quick explanation about how the testing itself works… You predict what a function will do, and then compare your result with the functions. This sounds a lot more complicated than it is.

Code: (Plain Text)
  1. $this->assertEqual(5, function_that_returns_five());

There are various different assert functions you can use (plus write your own), but the general idea is the internals of SimpleTest compare the two values, and adds it to the error log if the assertion fails. Aside from basic value comparison, you can also tell it to expect error messages ,exceptions, or the lack thereof. Here is a list of commonly used assertions:

assertTrue($x) Fail if $x is false
assertFalse($x) Fail if $x is true
assertNull($x) Fail if $x is set
assertNotNull($x) Fail if $x not set
assertIsA($x, $t) Fail if $x is not the class or type $t
assertEqual($x, $y) Fail if $x == $y is false
assertNotEqual($x, $y) Fail if $x == $y is true
assertIdentical($x, $y) Fail if $x === $y is false
assertNotIdentical($x, $y) Fail if $x === $y is true
assertReference($x, $y) Fail unless $x and $y are the same variable
assertCopy($x, $y) Fail if $x and $y are the same variable
assertWantedPattern($p, $x) Fail unless the regex $p matches $x
assertNoUnwantedPattern($p, $x) Fail if the regex $p matches $x
assertNoErrors() Fail if any PHP error occoured
assertError($x) Fail if no PHP error or incorrect message

The next parameter of each function is an additional message you can add to the report. Going back to our array example, here is the basic structure for the file we will use:

Code: (Plain Text)
  1. class Test_Array_Functions extends UnitTestCase
  2. {
  3.     private $subject_a;
  4.     private $subject_b;
  5.     private $subject_c;
  6.     private $subject_z;
  7.  
  8.     public function __construct()
  9.     {
  10.         $this->reset_state();
  11.     }
  12.  
  13.     private function reset_state()
  14.     {
  15.         $this->subject_a = array(‘a’, ‘b’, ‘c’, ‘d’);
  16.         $this->subject_b = array(1, 2, 3, 4);
  17.         $this->subject_c = array_combine($this->subject_a, $this->subject_b);
  18.         $this->subject_z = 3;
  19.     }
  20.  
  21.     // tests here
  22.  
  23. }

The only important thing here is we are working with a class, not a function, and that the class has to extend UnitTestCase (or another SimpleTest test unit class). In this example, since we will be worknig with arrays, I set up 4 sample variables we will use, and a function to reset them often so our tests are consistent. For a test function to be ran, the function must start with “test”.

Here we will test the array_push function a few different ways: test that it adds one element, test that the new element is added properly, test that the return value is the new count, and test that passing no arguments will cause an error. We could test it a few other ways as well, but for the sake of example we won’t go into too much detail here.

Code: (Plain Text)
  1. function test_array_push()
  2. {
  3.     $before = $this->subject_a;
  4.     $before_count = count($this->subject_a);
  5.  
  6.     $return = array_push($this->subject_a, ‘e’);
  7.  
  8.     $this->assertTrue($before_count + 1 == $return, ‘count incremented by 1′);
  9.  
  10.     $this->assertTrue($return == count($this->subject_a), ‘counts match’);
  11.  
  12.     $this->assertIdentical(
  13.     $this->subject_a,
  14.         array(‘a’, ‘b’, ‘c’, ‘d’, ‘e’),
  15.         ‘contains correct elements’
  16.     );
  17.  
  18.     array_push();
  19.     $this->assertError(‘Wrong parameter count for array_push()’);
  20.  
  21.     $this->reset_state();
  22. }

Our test here accomplishes all of that quite easily, so let’s test the count() function. It should 1) return the right number of elements, 2) generate an error without any parameters, and 3) not generate an error with invalid parameters.

Code: (Plain Text)
  1. function test_count()
  2. {
  3.     $counter = 0;
  4.  
  5.     foreach ($this->subject_a as $value)
  6.     {
  7.         $counter++;
  8.     }
  9.  
  10.     $this->assertEqual($counter, count($this->subject_a));
  11.  
  12.     count();
  13.     $this->assertError(‘count() expects at least 1 parameter, 0 given’);
  14.  
  15.     count(0);
  16.     $this->assertNoErrors();
  17. }

Again, these were quite easy tasks. The only thing new here is testing for errors. SimpleTest will remember all the recent PHP errors, and then when you assertError, it will check for that error message in the queue. Or for no errors (since the last check).

Now, this obviously applies more when using your own functions, and not pre-made ones, so what you have to do is decide exactly how each function should work; which parameters it should expect, what it should return, and what other dependencies (global vars, properties, files, etc.) it requires to run. You can then build your tests to enforce these conditions and keep refactoring your code until they all pass and continue to pass.

Summary: Write code in small, testable units. Test these units separately, and as strictly as possible for incorrect and undocumented behavior. Refactor code as necessary. Writing the tests takes some practice, but it saves a LOT of time in the long run with debugging or making any changes.


No Responses to “Unit Testing Introduction”  

  1. No Comments

Leave a Reply