1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
20:
21: include_once dirname(__FILE__) . DS . 'bake.php';
22:
23: 24: 25: 26: 27: 28:
29: class TestTask extends BakeTask {
30:
31: 32: 33: 34: 35: 36:
37: var $path = TESTS;
38:
39: 40: 41: 42: 43: 44:
45: var $tasks = array('Template');
46:
47: 48: 49: 50: 51: 52:
53: var $classTypes = array('Model', 'Controller', 'Component', 'Behavior', 'Helper');
54:
55: 56: 57: 58: 59: 60:
61: var $_fixtures = array();
62:
63:
64: 65: 66: 67: 68:
69: function execute() {
70: if (empty($this->args)) {
71: $this->__interactive();
72: }
73:
74: if (count($this->args) == 1) {
75: $this->__interactive($this->args[0]);
76: }
77:
78: if (count($this->args) > 1) {
79: $type = Inflector::underscore($this->args[0]);
80: if ($this->bake($type, $this->args[1])) {
81: $this->out('done');
82: }
83: }
84: }
85:
86: 87: 88: 89: 90:
91: function __interactive($type = null) {
92: $this->interactive = true;
93: $this->hr();
94: $this->out(__('Bake Tests', true));
95: $this->out(sprintf(__("Path: %s", true), $this->path));
96: $this->hr();
97:
98: if ($type) {
99: $type = Inflector::camelize($type);
100: if (!in_array($type, $this->classTypes)) {
101: $this->error(sprintf('Incorrect type provided. Please choose one of %s', implode(', ', $this->classTypes)));
102: }
103: } else {
104: $type = $this->getObjectType();
105: }
106: $className = $this->getClassName($type);
107: return $this->bake($type, $className);
108: }
109:
110: 111: 112: 113: 114: 115: 116:
117: function bake($type, $className) {
118: if ($this->typeCanDetectFixtures($type) && $this->isLoadableClass($type, $className)) {
119: $this->out(__('Bake is detecting possible fixtures..', true));
120: $testSubject =& $this->buildTestSubject($type, $className);
121: $this->generateFixtureList($testSubject);
122: } elseif ($this->interactive) {
123: $this->getUserFixtures();
124: }
125: $fullClassName = $this->getRealClassName($type, $className);
126:
127: $methods = array();
128: if (class_exists($fullClassName)) {
129: $methods = $this->getTestableMethods($fullClassName);
130: }
131: $mock = $this->hasMockClass($type, $fullClassName);
132: $construction = $this->generateConstructor($type, $fullClassName);
133:
134: $plugin = null;
135: if ($this->plugin) {
136: $plugin = $this->plugin . '.';
137: }
138:
139: $this->Template->set('fixtures', $this->_fixtures);
140: $this->Template->set('plugin', $plugin);
141: $this->Template->set(compact('className', 'methods', 'type', 'fullClassName', 'mock', 'construction'));
142: $out = $this->Template->generate('classes', 'test');
143:
144: $filename = $this->testCaseFileName($type, $className);
145: $made = $this->createFile($filename, $out);
146: if ($made) {
147: return $out;
148: }
149: return false;
150: }
151:
152: 153: 154: 155: 156: 157:
158: function getObjectType() {
159: $this->hr();
160: $this->out(__("Select an object type:", true));
161: $this->hr();
162:
163: $keys = array();
164: foreach ($this->classTypes as $key => $option) {
165: $this->out(++$key . '. ' . $option);
166: $keys[] = $key;
167: }
168: $keys[] = 'q';
169: $selection = $this->in(__("Enter the type of object to bake a test for or (q)uit", true), $keys, 'q');
170: if ($selection == 'q') {
171: return $this->_stop();
172: }
173: return $this->classTypes[$selection - 1];
174: }
175:
176: 177: 178: 179: 180: 181: 182:
183: function getClassName($objectType) {
184: $type = strtolower($objectType);
185: if ($this->plugin) {
186: $path = Inflector::pluralize($type);
187: if ($type === 'helper') {
188: $path = 'views' . DS . $path;
189: } elseif ($type === 'component') {
190: $path = 'controllers' . DS . $path;
191: } elseif ($type === 'behavior') {
192: $path = 'models' . DS . $path;
193: }
194: $options = App::objects($type, App::pluginPath($this->plugin) . $path, false);
195: } else {
196: $options = App::objects($type);
197: }
198: $this->out(sprintf(__('Choose a %s class', true), $objectType));
199: $keys = array();
200: foreach ($options as $key => $option) {
201: $this->out(++$key . '. ' . $option);
202: $keys[] = $key;
203: }
204: $selection = $this->in(__('Choose an existing class, or enter the name of a class that does not exist', true));
205: if (isset($options[$selection - 1])) {
206: return $options[$selection - 1];
207: }
208: return $selection;
209: }
210:
211: 212: 213: 214: 215: 216: 217: 218: 219:
220: function typeCanDetectFixtures($type) {
221: $type = strtolower($type);
222: return ($type == 'controller' || $type == 'model');
223: }
224:
225: 226: 227: 228: 229: 230: 231: 232:
233: function isLoadableClass($type, $class) {
234: return App::import($type, $class);
235: }
236:
237: 238: 239: 240: 241: 242: 243: 244: 245:
246: function &buildTestSubject($type, $class) {
247: ClassRegistry::flush();
248: App::import($type, $class);
249: $class = $this->getRealClassName($type, $class);
250: if (strtolower($type) == 'model') {
251: $instance =& ClassRegistry::init($class);
252: } else {
253: $instance =& new $class();
254: }
255: return $instance;
256: }
257:
258: 259: 260: 261: 262: 263: 264: 265:
266: function getRealClassName($type, $class) {
267: if (strtolower($type) == 'model') {
268: return $class;
269: }
270: return $class . $type;
271: }
272:
273: 274: 275: 276: 277: 278: 279: 280:
281: function getTestableMethods($className) {
282: $classMethods = get_class_methods($className);
283: $parentMethods = get_class_methods(get_parent_class($className));
284: $thisMethods = array_diff($classMethods, $parentMethods);
285: $out = array();
286: foreach ($thisMethods as $method) {
287: if (substr($method, 0, 1) != '_' && $method != strtolower($className)) {
288: $out[] = $method;
289: }
290: }
291: return $out;
292: }
293:
294: 295: 296: 297: 298: 299: 300: 301:
302: function generateFixtureList(&$subject) {
303: $this->_fixtures = array();
304: if (is_a($subject, 'Model')) {
305: $this->_processModel($subject);
306: } elseif (is_a($subject, 'Controller')) {
307: $this->_processController($subject);
308: }
309: return array_values($this->_fixtures);
310: }
311:
312: 313: 314: 315: 316: 317: 318: 319:
320: function _processModel(&$subject) {
321: $this->_addFixture($subject->name);
322: $associated = $subject->getAssociated();
323: foreach ($associated as $alias => $type) {
324: $className = $subject->{$alias}->name;
325: if (!isset($this->_fixtures[$className])) {
326: $this->_processModel($subject->{$alias});
327: }
328: if ($type == 'hasAndBelongsToMany') {
329: $joinModel = Inflector::classify($subject->hasAndBelongsToMany[$alias]['joinTable']);
330: if (!isset($this->_fixtures[$joinModel])) {
331: $this->_processModel($subject->{$joinModel});
332: }
333: }
334: }
335: }
336:
337: 338: 339: 340: 341: 342: 343: 344:
345: function _processController(&$subject) {
346: $subject->constructClasses();
347: $models = array(Inflector::classify($subject->name));
348: if (!empty($subject->uses)) {
349: $models = $subject->uses;
350: }
351: foreach ($models as $model) {
352: $this->_processModel($subject->{$model});
353: }
354: }
355:
356: 357: 358: 359: 360: 361: 362: 363:
364: function _addFixture($name) {
365: $parent = get_parent_class($name);
366: $prefix = 'app.';
367: if (strtolower($parent) != 'appmodel' && strtolower(substr($parent, -8)) == 'appmodel') {
368: $pluginName = substr($parent, 0, strlen($parent) -8);
369: $prefix = 'plugin.' . Inflector::underscore($pluginName) . '.';
370: }
371: $fixture = $prefix . Inflector::underscore($name);
372: $this->_fixtures[$name] = $fixture;
373: }
374:
375: 376: 377: 378: 379: 380:
381: function getUserFixtures() {
382: $proceed = $this->in(__('Bake could not detect fixtures, would you like to add some?', true), array('y','n'), 'n');
383: $fixtures = array();
384: if (strtolower($proceed) == 'y') {
385: $fixtureList = $this->in(__("Please provide a comma separated list of the fixtures names you'd like to use.\nExample: 'app.comment, app.post, plugin.forums.post'", true));
386: $fixtureListTrimmed = str_replace(' ', '', $fixtureList);
387: $fixtures = explode(',', $fixtureListTrimmed);
388: }
389: $this->_fixtures = array_merge($this->_fixtures, $fixtures);
390: return $fixtures;
391: }
392:
393: 394: 395: 396: 397: 398: 399: 400:
401: function hasMockClass($type) {
402: $type = strtolower($type);
403: return $type == 'controller';
404: }
405:
406: 407: 408: 409: 410: 411: 412: 413:
414: function generateConstructor($type, $fullClassName) {
415: $type = strtolower($type);
416: if ($type == 'model') {
417: return "ClassRegistry::init('$fullClassName');\n";
418: }
419: if ($type == 'controller') {
420: $className = substr($fullClassName, 0, strlen($fullClassName) - 10);
421: return "new Test$fullClassName();\n\t\t\$this->{$className}->constructClasses();\n";
422: }
423: return "new $fullClassName();\n";
424: }
425:
426: 427: 428: 429: 430: 431: 432: 433: 434:
435: function testCaseFileName($type, $className) {
436: $path = $this->getPath();;
437: $path .= 'cases' . DS . strtolower($type) . 's' . DS;
438: if (strtolower($type) == 'controller') {
439: $className = $this->getRealClassName($type, $className);
440: }
441: return $path . Inflector::underscore($className) . '.test.php';
442: }
443:
444: 445: 446: 447: 448: 449:
450: function help() {
451: $this->hr();
452: $this->out("Usage: cake bake test <type> <class>");
453: $this->hr();
454: $this->out('Commands:');
455: $this->out("");
456: $this->out("test model post\n\tbakes a test case for the post model.");
457: $this->out("");
458: $this->out("test controller comments\n\tbakes a test case for the comments controller.");
459: $this->out("");
460: $this->out('Arguments:');
461: $this->out("\t<type> Can be any of the following 'controller', 'model', 'helper',\n\t'component', 'behavior'.");
462: $this->out("\t<class> Any existing class for the chosen type.");
463: $this->out("");
464: $this->out("Parameters:");
465: $this->out("\t-plugin CamelCased name of plugin to bake tests for.");
466: $this->out("");
467: $this->_stop();
468: }
469: }
470: