Approaching bliss; TDD+PHP+MVC

Dec 12, 2009

Started getting back into CodeIgnitor, but the one thing I was really missing from Rails is a test framework. As much as it doesn’t seem to have the same ongoing support and uptake as PHPUnit, I have a certain affinity to the SimpleTest framework. I decided to see what ramblings there were regarding TDD and CodeIgnitor on the interweb. In my search I ran across the article Setting up the perfect CodeIgniter & TDD Environment. I downloaded the code and massaged it a little to suit my needs. I’m not fully content with the implementation of BaseTestPath, but I’m sure a round of TDD (which I should’ve started with) will probably iron it out. Kudos to Jamie for doing the heavy lifting! It hasn’t been thoroughly tested, so use at your own peril. Specifically I haven’t verified the views section, but upon initial inspection unit tests work. I’m thinking generate and destroy functions that build out the skelton for models, controllers, views might be my next step.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
<?php
/*
SimpleTest + CodeIgniter

test.php

the test runner - loads all needed files, integrates with CodeIgniter and runs the tests 

Written by Jamie Rumbelow
http://jamierumbelow.net/

Modifications by Nathan Fisher
http://junctionbox.ca/

Notes on Directory structure; where ./ is the root of a fresh CI project.

./test.php
./system/test # symlink or similar to root of SimpleTest folder
./system/application/tests
./system/application/tests/models
./system/application/tests/views
./system/application/tests/controllers

Changes:

  * Added console writer for test suite.
  * Moved all of the file scanning into classes.
  * Fixed a bug where vim swp files are treated as test classes.
  * Fixed a bug where view files can never exclusively be processed.

Todo:

  * Unit Tests - how ironic :(
  * Fix potential problem if test folders do not exist.

License:

Free to use however you please... if it causes harm in anyway the authors are not liable.


*/

//Configure and load files
define('ROOT', dirname(__FILE__) . '/');
define('APP_ROOT', ROOT . 'system/application/');

require_once ROOT . 'system/test/unit_tester.php';
require_once ROOT . 'system/test/web_tester.php';
require_once ROOT . 'system/test/reporter.php';

class CodeIgniterUnitTestCase extends UnitTestCase {
  protected $ci;

  public function __construct() {
    parent::UnitTestCase();
    $this->ci =& get_instance();
  }
}

class CodeIgniterWebTestCase extends WebTestCase {
  protected $ci;

  public function __construct() {
    parent::WebTestCase();
    $this->ci =& get_instance();
  }
}

function add_full_path( &$v, $k, $o ) {
  $o->addImplementationPath($o->getImplementationPath($v));
  $v = $o->getTestPath() . '/' . $v;
}

function filter_hidden( $v ) {
  if( preg_match('/^\./', $v) ) {
    return FALSE;
  }

  return TRUE;
}


/**
 * BaseTestPath:
 *
 */
abstract class BaseTestPath {
  abstract function getTestPath();
  abstract function getImplementationPath($test);

  var $is_fullpath = FALSE;
  var $filenames = null;
  var $impl_filenames = array();

  function getFilenames() {
    if( $this->filenames === null ) {
      $this->filenames = @scandir($this->getTestPath());
      $this->filenames = array_filter( $this->filenames, 'filter_hidden' );
    }
    return $this->filenames;
  }

  function addToTestSuite( &$test ) {
    $this->loadImplementations();
    foreach( $this->getFilenamesWithFullPath() as $test_file ) {
      $test->addFile( $test_file );
    }
  }

  function loadImplementations() {
    $this->getFilenamesWithFullPath();
    foreach( $this->impl_filenames as $impl ) {
      if( file_exists($impl) ) {
        require_once($impl);
      }
    }
  }

  function getFilenamesWithFullPath() {
    if( $this->is_fullpath == FALSE ) {
      $this->getFilenames();
      array_walk( $this->filenames, 'add_full_path', $this );
      $this->is_fullpath = TRUE;
    }

    return $this->filenames;
  }

  function addImplementationPath( $impl_path ) {
    array_push($this->impl_filenames, $impl_path);
  }
}


/**
 * ControllerTestPath:
 *
 */
class ControllerTestPath extends BaseTestPath {
  function getTestPath() {
    return APP_ROOT . 'tests/controllers';
  }

  function getImplementationPath( $test_filename ) {
    $controller = preg_replace( '#.*?([a-zA-Z0-9_\-]+)_controller_test.php$#', '$1.php', $test_filename );
    return APP_ROOT . 'controller/' . $controller;
  }
}


/**
 * ModelTestPath:
 *
 */
class ModelTestPath extends BaseTestPath {
  function getTestPath() {
    return APP_ROOT . 'tests/models';
  }

  function getImplementationPath($test_filename) {
    $model = preg_replace('#.*?([a-zA-Z0-9_\-]+_model)_test.php$#', '$1.php', $test_filename);
    return APP_ROOT . 'models/' . $model;
  }
}


/**
 * ViewTestPath:
 *
 */
class ViewTestPath extends BaseTestPath {
  function getTestPath() {
    return APP_ROOT . 'tests/views';
  }

  function getImplementationPath( $test_filename ) {
    $view = preg_replace('#.*?([a-zA-Z0-9_\-]+)_view_test.php$#', '$1.php', $test_filename);
    $view = implode( '/', explode('_',$view) );
    return APP_ROOT . 'views/' . $view;
  }
}


//Capture CodeIgniter output, discard and load system into $CI variable
ob_start();
include(ROOT . 'index.php');
$CI =& get_instance();
ob_end_clean();

//Setup the test suite
$test_suite =& new TestSuite();
$test_suite->_label = 'CodeIgniter Application Test Suite';

$controller_tests = new ControllerTestPath();
$model_tests = new ModelTestPath();
$view_tests = new ViewTestPath();
$tests = array();

if( isset($_GET['controllers']) ) {
  array_push( $tests, $controller_tests );
}

if( isset($_GET['models']) ) {
  array_push( $tests, $model_tests );
}

if( isset($_GET['views']) ) {
  array_push( $tests, $view_tests );
}

if( sizeof($tests) == 0 ) {
  $tests = array( $model_tests, $view_tests, $controller_tests );
}

foreach( $tests as $test ) {
  $test->addToTestSuite( &$test_suite );
}

//Run tests!
if (TextReporter::inCli()) {
  exit ($test_suite->run(new TextReporter()) ? 0 : 1);
}
$test_suite->run(new HtmlReporter());

/* End of file test.php */
/* Location: ./test.php */

tags: [ php testing ]