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

[eluser]t'mo[/eluser]
(continued from Part I)

Last time I shared how I made a Controller subclass that had some helpful methods for simplifying use of CI's Unit Testing class. The primary benefit was that you could point your browser to your test class and see your test results nicely formatted in the browser:

Code:
http://localhost/path/to/app/test/something

One downside, though, is that you have to remember all the "somethings" you've written tests for and point your browser at each of them. What I really wanted was a way to see *all* the results of *all* my test classes together.

Enter the command line. Following the lead from an entry in the wiki (cron scripts), I saw it would be possible, with a little modification, to create a version of "index.php" that would invoke an arbitrary path into my application. I created a copy of index.php called "cmdline.php" and added one line to set the path into my app:

Code:
...
$_SERVER['PATH_INFO'] = $argv[1]; // <-- My new addition

// next line is where CI gets its start
require_once BASEPATH.'codeigniter/CodeIgniter'.EXT;
?&gt;

For example, I run my "Product" model's test class from the command line like this:

Code:
$ php cmdline.php /test/products

From here it was just a short step to producing a shell script that would execute all my tests:

Code:
#!/bin/sh

for f in `ls controllers/test`; do
  g=/test/`echo $f | sed 's/.php//'`
  php cmdline.php $g
done

While running my test suite on the command line, I don't want to see HTML output - it's too hard to parse visually. I hinted at this in part I, but didn't explain. You may recall this snippet from MY_TestController::index:

Code:
if ($_SERVER['SCRIPT_NAME'] == 'cmdline.php') { // YES! this is the line; pay attention, grasshopper
      $this->load->view('test/simpleResults', $data);
    }
    else ...

So, I just load a different view, depending on where I'm running the test from. The simplified view says either "OK" or "Failed" for each test (plus a handy 'print_r' on the failed response):

Code:
=========================================================
Unit test results for &lt;?=$modelname."\n"?&gt;
=========================================================
&lt;?php
foreach ($results as $result) {
  if ($result['Result'] == 'Passed') {
    print "OK: " . $result['Test Name'] . "\n";
  }
  else {
    print "Failed: ";
    print_r($result);
  }
}
?&gt;

Quick, easy, mostly painless, eh? Well, unless you're on Windows...then I don't know what you'll do for a "shell". ;-)

---

I originally planned on stopping the "unit testing help" series here, but questions in another thread about "how to test the front end", plus some neat stuff I saw at work the other day in Ruby, have prompted me to look at how I might do automated acceptance testing with CI...so there will be more to come.
#2

[eluser]redguy[/eluser]
I'm originally a Java developer, and there I use JUnit to run my test-cases. I couldn't do what I wanted to do with the Unit Testing class in CI, so I created a helper:

Code:
&lt;?
    function assert_true($assertion) {
        if($assertion) {
            return TRUE;
        } else {
            return FALSE;
        }
    }
    
    function assert_false($assertion) {
        if($assertion) {
            return FALSE;
        } else {
            return TRUE;
        }
    }
    
    function assert_equals($base, $check) {
        if($base === $check) {
            return TRUE;
        } else {
            return FALSE;
        }
    }
    
    function assert_not_equals($base, $check) {
        if($base !== $check) {
            return TRUE;
        } else {
            return FALSE;
        }
    }
    
    function assert_not_empty($assertion) {
        if(!empty($assertion)) {
            return TRUE;
        } else {
            return FALSE;
        }
    }
    
    function assert_empty($assertion) {
        if(empty($assertion)) {
            return TRUE;
        } else {
            return FALSE;
        }
    }
    
    function unit_test_report($data) {        
        $str = '<table border="1">';
        $str .= '<tr><th>Test</th><th>Name</th><th>Result</th></tr>';
        foreach($data as $key => $test){
            $str .= '<tr><td>'.$key.'</td><td>'.$test['Test Name'].'</td>';
            $str .= '<td style="background-color:'.($test['Result'] == 'Passed' ? '#00FF00' : '#FF0000').';">'.$test['Result'].'</td></tr>';
        }
        $str .= '</table>';
        
        return $str;
    }
?&gt;

I call the following method from within an index() in a test controller (so I can access it with my browser):
Code:
function _test_categories_model() {
        $this->load->model('Categories_model', 'categories');
        
        $data1['name'] = 'foo';
        $cid = $this->categories->insert_category($data1);
        $this->unit->run(assert_not_empty($cid), TRUE, 'Add a category');
        
        $this->unit->run(assert_not_empty($this->categories->get_category_by_id($cid)), TRUE, "Select category with cid [$cid]");
        
        $data2['name'] = 'bar';
        $this->unit->run(assert_not_empty($this->categories->update_category($cid, $data2)), TRUE, "Update category with cid [$cid]");
        
        $this->unit->run(assert_not_empty($this->categories->get_all_categories()), TRUE, "Update category with cid [$cid]");
        
        $this->unit->run(assert_not_empty($this->categories->remove_category($cid)), TRUE, "Removing category with cid [$cid]");
        
        print unit_test_report($this->unit->result());
    }

That gives me the following HTML:
Code:
<table border="1"><tr><th>Test</th><th>Name</th><th>Result</th></tr><tr><td>0</td><td>Add a category</td><td style="background-color:#00FF00;">Passed</td></tr><tr><td>1</td><td>Select category with cid [24]</td><td style="background-color:#00FF00;">Passed</td></tr><tr><td>2</td><td>Update category with cid [24]</td><td style="background-color:#00FF00;">Passed</td></tr><tr><td>3</td><td>Update category with cid [24]</td><td style="background-color:#00FF00;">Passed</td></tr><tr><td>4</td><td>Removing category with cid [24]</td><td style="background-color:#00FF00;">Passed</td></tr></table>

I'm thinking about of putting the $this->unit->result() of every test-case into 1 Array, so I can make my own test-suite. If I pass that test-suite to unit_test_report() I can (kind of) automate my unit testing.

By the way, I'm sure my PHP can be improved, I've just started a few months ago so I don't know every in and out of the language.
#3

[eluser]t'mo[/eluser]
[quote author="redguy" date="1208195591"]I'm originally a Java developer, and there I use JUnit to run my test-cases. I couldn't do what I wanted to do with the Unit Testing class in CI...

I call the following method from within an index() in a test controller (so I can access it with my browser)...

I'm thinking about of putting the $this->unit->result() of every test-case into 1 Array, so I can make my own test-suite. If I pass that test-suite to unit_test_report() I can (kind of) automate my unit testing.

By the way, I'm sure my PHP can be improved, I've just started a few months ago so I don't know every in and out of the language.[/quote]

I know what you mean about things JUnit can do that CI's Unit Testing class can't (yet) do. That's why I started this series of posts. I ended up doing similar things that you have done(showing red/green bars in the browser, hence an "index()" method, etc.). Did you see my first post on this topic?
;-)

As far as how much "improvement" PHP needs, I, like you, don't have a long history with the language. I'm still trying to differentiate what works best with straight PHP code vs. what works best in a library or a framework. The fact is that CI has the Unit Testing class. Maybe this is good, and the responsibility belongs with CI, but maybe it's not, and testing-specific things ought to be factored out to a separate library. (E.g., in Java, JUnit is not part a web framework, such as Struts.)

There are other unit-testing libraries for PHP, but for now, I'm using the Unit Testing class, because it's there, and because with a little help, it's worked out well.
#4

[eluser]redguy[/eluser]
Yup, I did check your first post (why 2 posts actually?). I saw that your design behind the code is very similar to my design: having a sort of test-suite so you can automate the unit testing. It's to bad I run on a Windows box, else I might've given it a try. I also don't have that much time assigned to delving into it. If I have some time left in this iteration I might look into it a little deeper.

But atleast now I get a nice table with green/red cells indicating test status Smile.
#5

[eluser]t'mo[/eluser]
[quote author="redguy" date="1208257716"]Yup, I did check your first post (why 2 posts actually?). I saw that your design behind the code is very similar to my design: having a sort of test-suite so you can automate the unit testing. It's to bad I run on a Windows box, else I might've given it a try. I also don't have that much time assigned to delving into it. If I have some time left in this iteration I might look into it a little deeper.

But atleast now I get a nice table with green/red cells indicating test status Smile.[/quote]

Why two posts? Because [strike]I'm making this up as I go[/strike] my approach to unit testing with PHP/CI keeps evolving... ;-) (Watch for Part III, coming soon.)

If you have php.exe in your path, you should be able to invoke the following (save it, call it 'test.php' or something) to run all your tests:

Code:
&lt;?php

$dir = opendir('controllers/test');

while (false !== ($f = readdir($dir))) {
  if ('.' == substr($f, 0, 1)) {
    continue;
  }
  $g = '/test/' . str_replace('.php', '', $f);
  print `php cmdline.php $g`;
}

closedir($dir);

?&gt;

Invoke it thusly:

Code:
C:\>php test.php

Note that I've only tested this on a Linux box, not windows (I don't know if my wife would look kindly on having a LAMP stack appear on the family computer, so here I sit in the basement w/my boxen...) But it should work.
#6

[eluser]tomcode[/eluser]
Quote:I don’t know if my wife would look kindly on having a LAMP stack appear on the family computer, so here I sit in the basement w/my boxen...
You could use EasyPHP which can run without installation from a USB Flash Drive.
#7

[eluser]JensRoland[/eluser]
Great post! T'mo and redguy, you guys just ROCK!!!

All right - I've finished my unit testing suite, based on the code from this post (although I've added a lot to it now). I've decided to call it Toast, since Test seemed too generic, and Test_Suite_Built_On_T'mo's_Code_With_Redguy's_Functions_And_Expanded_By_Me just seemed a bit... wordy. What I'm trying to say is, I didn't name it and put it on my website to steal any thunder from you two - you are awesome, and all credit for the base of this goes to you - but it just needed a name, and 'Toast' seemed nice and friendly.

So anyway, here goes:

Toast - Unit testing for CodeIgniter

* lightweight and unobtrusive - the entire suite has just 2 code files, and one of them is optional ;-)

* works with CI with no changes - just drop it in and it works

* simple JUnit-style syntax

* works entirely in the browser - no need for shell access

* run single tests, sets of tests, or all tests at once (all in the browser)

* auto-benchmarking using CI's benchmarking class

Here's an example test class - hopefully enough to get you interested:

Code:
require_once(APPPATH . '/controllers/test/Toast.php');

class My_test_class extends Toast
{

    function My_test_class()
    {
        parent::Toast(__FILE__); // Remember this
    }

    function test_some_action()
    {
        // Test code goes here
        $my_var = 2 + 2;
        $this->_assert_equals($my_var, 4);
    }

    function test_some_other_action()
    {
        // Test code goes here
        $my_var = true;
        $this->_assert_false($my_var);
    }

}

Anyway, I've put the source code up for download from my site, along with a simple user guide.

Toast v1.1 - Unit Testing suite for CodeIgniter

I'm hoping it can make it easier for people to do proper Test-Driven Development in CodeIgniter, since CodeIgniter rocks big time, and TDD is a rockin' way to code.

All the best-

/Jens Roland
#8

[eluser]JensRoland[/eluser]
(deprecated)
#9

[eluser]JensRoland[/eluser]
(deprecated)




Theme © iAndrew 2016 - Forum software by © MyBB