Welcome Guest, Not a member yet? Register   Sign In
Some help for Unit Testing, part I
#1

[eluser]t'mo[/eluser]
I've been on an agile software development team for nearly three years where we try to never write a line of production code without first having unit tests. This holds true for our main work (Java), but I've seen the practice of TDD usefully extended to other realms (even Perl!). With this in mind, and even though I'm finding using CI at home on personal projects very satisfying, I'm not happy with CI's unit testing capabilities.

I don't know how to remedy the entire situation, but I offer this as at least a little bit of help; use it if you like, tell me where it could be improved, or show me a better way.

My pain started with the repetition in using CI's unit test class. I repeated "$this->load->library('unit_test')" in every test class, always created an "index()" method that would invoke each test, always called "$this->unit->report()", etc. That irritation festered until I came up with this:

Code:
<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');

class MY_TestController extends Controller
{
  var $modelname;

  function MY_TestController($name)
  {
    parent::Controller();
    $this->load->library('unit_test');
    $this->modelname = $name;
  }

  function index()
  {
    $this->_runAll();
    $data['modelname'] = $this->modelname;
    $data['results'] = $this->unit->result();
    if ($_SERVER['SCRIPT_NAME'] == 'cmdline.php') { // "Aah! What is this?" you ask. Wait for part II, grasshopper.
      $this->load->view('test/simpleResults', $data);
    }
    else {
      $this->load->view('test/results', $data);
    }
  }

  function _runAll()
  {
    foreach ($this->_getTestMethods() as $method) {
      $this->$method();
    }
  }

  function _getTestMethods()
  {
    $methods = get_class_methods($this);
    $testMethods = array();
    foreach ($methods as $method) {
      if (substr(strtolower($method), 0, 4) == 'test') {
        $testMethods[] = $method;
      }
    }
    return $testMethods;
  }
}

?>

So, using that controller, I create a subclass that looks like so:

Code:
<?php
require_once(APPPATH . '/libraries/MY_TestController.php');
// requiring that was another CI irritation...but I'll save that for another day

class TheModelTest extends MY_TestController
{
  function TheModelTest()
  {
    parent::MY_TestController(__FILE__);
    // NB: you could also put 'TheModel' here...it's purpose is merely documentation
    $this->load->model('TheModel');
  }

  function testTheModelDoesSomething()
  {
    $expectedResult = array(1, 2, 3);
    $actualResult = $this->TheModel->doSomething();
    $this->unit->run($actualResult, $expectedResult, 'TheModel should return (1,2,3)');
  }

  function testTheModelDoesSomethingElse()
  {
    $expectedResult = array('a', 'b', 'c');
    $actualResult = $this->TheModel->doSomethingElse();
    $this->unit->run($actualResult, $expectedResult, 'TheModel should return (a,b,c)');
  }
}

?>

The key points in the concrete test class are:

1. the functions that have tests you want run are prefixed with the word "test" and are automatically run by the super-class (MY_TestController)
2. some of the extra test stuff (remembering to call "report()", etc.) is abstracted out where it doesn't get in your way of seeing what it is you're trying to test

"But you're not calling 'report()'!" Yes, I forgot I also did away with that. I didn't like the default report format -- too verbose. Instead I came up with this view ('view/test/results.php'), which seems simpler, and has the benefit of a handy "red/green bar" available from other, more traditional unit testing tools:

Code:
<html>
<head>
<title>Unit test results for <?=$modelname?></title>
<style type="text/css">
* { font-family: Arial, sans-serif; font-size: 9pt }
.err, .pas { color: white; font-weight: bold }
.err { background-color: red }
.pas { background-color: green }
</style>
</head>
<body>
<h1>Unit test results for &lt;?=$modelname?&gt;</h1>

<ol>
&lt;?php foreach ($results as $result): ?&gt;
<li>

&lt;?php if ($result['Result'] == 'Passed') { ?&gt;
<div class="pas">&lt;?=$result['Test Name']?&gt;</div>
&lt;?php } else { ?&gt;
<div class="err"><pre>&lt;?php print_r($result); ?&gt;</pre></div>
&lt;?php } ?&gt;

</li>
&lt;?php endforeach; ?&gt;
</ol>

&lt;/body&gt;
&lt;/html&gt;

So, what does that leave me in the end? A relatively quick, painless way to see my test results, in a format that's not too obtrusive. I just hit each test controller's URL, and with no more than a glance up at the monitor ("Anything red?"), I'm done.

Code:
http://localhost/app_root/test/themodeltest
http://localhost/app_root/test/another_model_test
...

After all, the whole concept of unit testing is useful only if it's able to give us quick feedback about the quality of our code. :-)




Theme © iAndrew 2016 - Forum software by © MyBB