2008-05-30 11:40:08 +00:00
< ? php
/**
* The TestTask handles creating and updating test files .
*
2010-10-03 16:38:58 +00:00
* PHP 5
2008-05-30 11:40:08 +00:00
*
2009-11-06 06:46:59 +00:00
* CakePHP ( tm ) : Rapid Development Framework ( http :// cakephp . org )
2010-01-26 19:18:20 +00:00
* Copyright 2005 - 2010 , Cake Software Foundation , Inc . ( http :// cakefoundation . org )
2008-05-30 11:40:08 +00:00
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice .
*
2010-01-26 19:18:20 +00:00
* @ copyright Copyright 2005 - 2010 , Cake Software Foundation , Inc . ( http :// cakefoundation . org )
2009-11-06 06:00:11 +00:00
* @ link http :// cakephp . org CakePHP ( tm ) Project
2010-12-24 19:26:26 +00:00
* @ package cake . console . shells . tasks
2009-07-17 03:55:41 +00:00
* @ since CakePHP ( tm ) v 1.3
2009-11-06 06:51:51 +00:00
* @ license MIT License ( http :// www . opensource . org / licenses / mit - license . php )
2008-05-30 11:40:08 +00:00
*/
2009-07-24 19:18:37 +00:00
2010-03-05 02:57:48 +00:00
include_once dirname ( __FILE__ ) . DS . 'bake.php' ;
2010-11-06 03:16:15 +00:00
App :: import ( 'Model' , 'ClassRegistry' );
2010-03-05 02:57:48 +00:00
2008-05-30 11:40:08 +00:00
/**
* Task class for creating and updating test files .
*
2010-12-24 19:26:26 +00:00
* @ package cake . console . shells . tasks
2008-05-30 11:40:08 +00:00
*/
2010-03-05 02:57:48 +00:00
class TestTask extends BakeTask {
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* path to TESTS directory
*
* @ var string
* @ access public
*/
2010-04-04 07:14:00 +00:00
public $path = TESTS ;
2009-05-25 00:12:17 +00:00
2009-05-25 03:50:21 +00:00
/**
* Tasks used .
*
* @ var array
2009-12-30 03:50:43 +00:00
* @ access public
2009-11-14 12:19:25 +00:00
*/
2010-04-04 07:14:00 +00:00
public $tasks = array ( 'Template' );
2009-07-24 19:18:37 +00:00
2009-05-25 00:12:17 +00:00
/**
* class types that methods can be generated for
*
* @ var array
2009-12-30 03:50:43 +00:00
* @ access public
2009-11-14 12:19:25 +00:00
*/
2010-04-04 07:14:00 +00:00
public $classTypes = array ( 'Model' , 'Controller' , 'Component' , 'Behavior' , 'Helper' );
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Internal list of fixtures that have been added so far .
*
* @ var string
2009-12-30 03:50:43 +00:00
* @ access protected
2009-11-14 12:19:25 +00:00
*/
2010-04-04 06:36:12 +00:00
protected $_fixtures = array ();
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Execution method always used for tasks
*
*/
2010-04-05 03:19:38 +00:00
public function execute () {
2010-10-19 03:09:23 +00:00
parent :: execute ();
2008-05-30 11:40:08 +00:00
if ( empty ( $this -> args )) {
2010-04-24 01:57:59 +00:00
$this -> _interactive ();
2008-05-30 11:40:08 +00:00
}
2008-09-24 13:49:37 +00:00
2008-05-30 11:40:08 +00:00
if ( count ( $this -> args ) == 1 ) {
2010-04-24 01:57:59 +00:00
$this -> _interactive ( $this -> args [ 0 ]);
2008-05-30 11:40:08 +00:00
}
if ( count ( $this -> args ) > 1 ) {
2009-05-25 03:50:21 +00:00
$type = Inflector :: underscore ( $this -> args [ 0 ]);
if ( $this -> bake ( $type , $this -> args [ 1 ])) {
2010-10-13 02:25:04 +00:00
$this -> out ( '<success>Done</success>' );
2008-05-30 11:40:08 +00:00
}
}
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Handles interactive baking
*
* @ access private
*/
2010-04-24 01:57:59 +00:00
protected function _interactive ( $type = null ) {
2009-05-25 03:50:21 +00:00
$this -> interactive = true ;
2008-05-30 11:40:08 +00:00
$this -> hr ();
2010-04-15 15:43:39 +00:00
$this -> out ( __ ( 'Bake Tests' ));
2010-12-05 01:37:13 +00:00
$this -> out ( __ ( 'Path: %s' , $this -> path ));
2008-05-30 11:40:08 +00:00
$this -> hr ();
2008-09-24 13:49:37 +00:00
2009-05-25 00:12:17 +00:00
if ( $type ) {
$type = Inflector :: camelize ( $type );
2009-05-25 03:50:21 +00:00
if ( ! in_array ( $type , $this -> classTypes )) {
2010-12-05 01:37:13 +00:00
$this -> error ( __ ( 'Incorrect type provided. Please choose one of %s' , implode ( ', ' , $this -> classTypes )));
2008-05-30 11:40:08 +00:00
}
2010-05-06 11:31:52 +00:00
} else {
2009-05-25 03:50:21 +00:00
$type = $this -> getObjectType ();
2009-05-25 00:12:17 +00:00
}
2009-05-25 03:50:21 +00:00
$className = $this -> getClassName ( $type );
return $this -> bake ( $type , $className );
2008-05-30 11:40:08 +00:00
}
2009-07-24 19:18:37 +00:00
2009-05-25 00:12:17 +00:00
/**
2009-05-25 03:50:21 +00:00
* Completes final steps for generating data to create test case .
2009-05-25 00:12:17 +00:00
*
2009-05-25 03:50:21 +00:00
* @ param string $type Type of object to bake test case for ie . Model , Controller
2009-07-24 19:18:37 +00:00
* @ param string $className the 'cake name' for the class ie . Posts for the PostsController
2009-05-25 03:50:21 +00:00
*/
2010-04-05 03:19:38 +00:00
public function bake ( $type , $className ) {
2009-05-25 03:50:21 +00:00
if ( $this -> typeCanDetectFixtures ( $type ) && $this -> isLoadableClass ( $type , $className )) {
2010-11-21 17:47:49 +00:00
$this -> out ( __ ( 'Bake is detecting possible fixtures...' ));
2010-11-13 04:05:44 +00:00
$testSubject = $this -> buildTestSubject ( $type , $className );
2009-05-25 03:50:21 +00:00
$this -> generateFixtureList ( $testSubject );
} elseif ( $this -> interactive ) {
$this -> getUserFixtures ();
}
$fullClassName = $this -> getRealClassName ( $type , $className );
2009-05-25 00:12:17 +00:00
2009-05-25 03:50:21 +00:00
$methods = array ();
if ( class_exists ( $fullClassName )) {
$methods = $this -> getTestableMethods ( $fullClassName );
2009-05-25 00:12:17 +00:00
}
2009-05-26 03:48:41 +00:00
$mock = $this -> hasMockClass ( $type , $fullClassName );
2009-05-25 04:54:23 +00:00
$construction = $this -> generateConstructor ( $type , $fullClassName );
$plugin = null ;
if ( $this -> plugin ) {
$plugin = $this -> plugin . '.' ;
}
2010-11-21 17:47:49 +00:00
$this -> out ( " \n Baking test case for $className $type ... " , 1 , Shell :: QUIET );
2009-05-25 03:50:21 +00:00
$this -> Template -> set ( 'fixtures' , $this -> _fixtures );
2009-05-25 04:54:23 +00:00
$this -> Template -> set ( 'plugin' , $plugin );
$this -> Template -> set ( compact ( 'className' , 'methods' , 'type' , 'fullClassName' , 'mock' , 'construction' ));
2009-07-01 04:50:38 +00:00
$out = $this -> Template -> generate ( 'classes' , 'test' );
2009-05-25 03:50:21 +00:00
2009-06-07 01:03:26 +00:00
$filename = $this -> testCaseFileName ( $type , $className );
$made = $this -> createFile ( $filename , $out );
2009-05-25 03:50:21 +00:00
if ( $made ) {
return $out ;
}
return false ;
2008-05-30 11:40:08 +00:00
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2009-05-25 03:50:21 +00:00
* Interact with the user and get their chosen type . Can exit the script .
2008-05-30 11:40:08 +00:00
*
2009-05-25 03:50:21 +00:00
* @ return string Users chosen type .
2009-11-14 12:19:25 +00:00
*/
2010-04-05 03:19:38 +00:00
public function getObjectType () {
2009-05-25 03:50:21 +00:00
$this -> hr ();
2010-04-15 15:43:39 +00:00
$this -> out ( __ ( 'Select an object type:' ));
2009-05-25 03:50:21 +00:00
$this -> hr ();
$keys = array ();
foreach ( $this -> classTypes as $key => $option ) {
$this -> out ( ++ $key . '. ' . $option );
$keys [] = $key ;
2008-05-30 11:40:08 +00:00
}
2009-05-25 03:50:21 +00:00
$keys [] = 'q' ;
2010-04-15 15:43:39 +00:00
$selection = $this -> in ( __ ( 'Enter the type of object to bake a test for or (q)uit' ), $keys , 'q' );
2009-05-25 03:50:21 +00:00
if ( $selection == 'q' ) {
return $this -> _stop ();
}
return $this -> classTypes [ $selection - 1 ];
}
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Get the user chosen Class name for the chosen type
*
* @ param string $objectType Type of object to list classes for i . e . Model , Controller .
* @ return string Class name the user chose .
2009-11-14 12:19:25 +00:00
*/
2010-04-05 03:19:38 +00:00
public function getClassName ( $objectType ) {
2010-12-14 03:00:37 +00:00
$type = strtolower ( $objectType );
if ( $this -> plugin ) {
$path = Inflector :: pluralize ( $type );
if ( $type === 'helper' ) {
$path = 'views' . DS . $path ;
} elseif ( $type === 'component' ) {
$path = 'controllers' . DS . $path ;
} elseif ( $type === 'behavior' ) {
$path = 'models' . DS . $path ;
}
$options = App :: objects ( $type , App :: pluginPath ( $this -> plugin ) . $path , false );
} else {
$options = App :: objects ( $type );
}
2010-12-05 01:37:13 +00:00
$this -> out ( __ ( 'Choose a %s class' , $objectType ));
2009-05-25 03:50:21 +00:00
$keys = array ();
foreach ( $options as $key => $option ) {
$this -> out ( ++ $key . '. ' . $option );
$keys [] = $key ;
}
2010-04-15 15:43:39 +00:00
$selection = $this -> in ( __ ( 'Choose an existing class, or enter the name of a class that does not exist' ));
2009-05-25 03:50:21 +00:00
if ( isset ( $options [ $selection - 1 ])) {
return $options [ $selection - 1 ];
}
return $selection ;
}
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Checks whether the chosen type can find its own fixtures .
* Currently only model , and controller are supported
2009-12-30 03:50:43 +00:00
*
* @ param string $type The Type of object you are generating tests for eg . controller
* @ param string $className the Classname of the class the test is being generated for .
2009-05-25 03:50:21 +00:00
* @ return boolean
2009-11-14 12:19:25 +00:00
*/
2010-04-05 03:19:38 +00:00
public function typeCanDetectFixtures ( $type ) {
2009-05-25 03:50:21 +00:00
$type = strtolower ( $type );
return ( $type == 'controller' || $type == 'model' );
}
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Check if a class with the given type is loaded or can be loaded .
*
2009-12-30 03:50:43 +00:00
* @ param string $type The Type of object you are generating tests for eg . controller
* @ param string $className the Classname of the class the test is being generated for .
2009-05-25 03:50:21 +00:00
* @ return boolean
2009-11-14 12:19:25 +00:00
*/
2010-04-05 03:19:38 +00:00
public function isLoadableClass ( $type , $class ) {
2009-05-25 03:50:21 +00:00
return App :: import ( $type , $class );
}
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Construct an instance of the class to be tested .
2009-05-25 04:54:23 +00:00
* So that fixtures can be detected
2009-05-25 03:50:21 +00:00
*
2009-12-30 03:50:43 +00:00
* @ param string $type The Type of object you are generating tests for eg . controller
* @ param string $class the Classname of the class the test is being generated for .
* @ return object And instance of the class that is going to be tested .
2009-11-14 12:19:25 +00:00
*/
2010-04-05 03:19:38 +00:00
public function & buildTestSubject ( $type , $class ) {
2009-05-26 04:36:25 +00:00
ClassRegistry :: flush ();
2009-05-25 03:50:21 +00:00
App :: import ( $type , $class );
$class = $this -> getRealClassName ( $type , $class );
if ( strtolower ( $type ) == 'model' ) {
2010-11-13 04:05:44 +00:00
$instance = ClassRegistry :: init ( $class );
2009-05-25 03:50:21 +00:00
} else {
2010-11-13 04:05:44 +00:00
$instance = new $class ();
2009-05-25 03:50:21 +00:00
}
return $instance ;
}
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Gets the real class name from the cake short form .
*
2009-12-30 03:50:43 +00:00
* @ param string $type The Type of object you are generating tests for eg . controller
* @ param string $class the Classname of the class the test is being generated for .
2009-05-25 03:50:21 +00:00
* @ return string Real classname
2009-11-14 12:19:25 +00:00
*/
2010-04-05 03:19:38 +00:00
public function getRealClassName ( $type , $class ) {
2009-05-25 03:50:21 +00:00
if ( strtolower ( $type ) == 'model' ) {
return $class ;
}
return $class . $type ;
2008-05-30 11:40:08 +00:00
}
2009-07-24 19:18:37 +00:00
2009-05-24 03:48:25 +00:00
/**
* Get methods declared in the class given .
* No parent methods will be returned
*
* @ param string $className Name of class to look at .
* @ return array Array of method names .
2009-11-14 12:19:25 +00:00
*/
2010-04-05 03:19:38 +00:00
public function getTestableMethods ( $className ) {
2009-05-24 03:48:25 +00:00
$classMethods = get_class_methods ( $className );
$parentMethods = get_class_methods ( get_parent_class ( $className ));
$thisMethods = array_diff ( $classMethods , $parentMethods );
$out = array ();
foreach ( $thisMethods as $method ) {
2009-08-02 22:17:31 +00:00
if ( substr ( $method , 0 , 1 ) != '_' && $method != strtolower ( $className )) {
2009-05-24 03:48:25 +00:00
$out [] = $method ;
}
}
return $out ;
}
2009-07-24 19:18:37 +00:00
2009-05-24 05:15:31 +00:00
/**
2009-05-24 05:30:04 +00:00
* Generate the list of fixtures that will be required to run this test based on
2009-05-24 05:15:31 +00:00
* loaded models .
*
2009-12-30 03:50:43 +00:00
* @ param object $subject The object you want to generate fixtures for .
2009-05-24 05:15:31 +00:00
* @ return array Array of fixtures to be included in the test .
2009-11-14 12:19:25 +00:00
*/
2011-03-26 04:04:25 +00:00
public function generateFixtureList ( $subject ) {
2009-05-24 05:15:31 +00:00
$this -> _fixtures = array ();
if ( is_a ( $subject , 'Model' )) {
$this -> _processModel ( $subject );
} elseif ( is_a ( $subject , 'Controller' )) {
$this -> _processController ( $subject );
}
return array_values ( $this -> _fixtures );
}
2009-07-24 19:18:37 +00:00
2009-05-24 05:15:31 +00:00
/**
2009-05-24 05:30:04 +00:00
* Process a model recursively and pull out all the
2009-05-24 05:15:31 +00:00
* model names converting them to fixture names .
*
2009-12-30 03:50:43 +00:00
* @ param Model $subject A Model class to scan for associations and pull fixtures off of .
2009-05-24 05:15:31 +00:00
* @ return void
2009-11-14 12:19:25 +00:00
*/
2011-03-26 04:04:25 +00:00
protected function _processModel ( $subject ) {
2009-05-24 05:15:31 +00:00
$this -> _addFixture ( $subject -> name );
$associated = $subject -> getAssociated ();
foreach ( $associated as $alias => $type ) {
$className = $subject -> { $alias } -> name ;
if ( ! isset ( $this -> _fixtures [ $className ])) {
$this -> _processModel ( $subject -> { $alias });
}
if ( $type == 'hasAndBelongsToMany' ) {
$joinModel = Inflector :: classify ( $subject -> hasAndBelongsToMany [ $alias ][ 'joinTable' ]);
if ( ! isset ( $this -> _fixtures [ $joinModel ])) {
$this -> _processModel ( $subject -> { $joinModel });
}
}
}
}
2009-07-24 19:18:37 +00:00
2009-05-24 05:30:04 +00:00
/**
* Process all the models attached to a controller
* and generate a fixture list .
*
2009-12-30 03:50:43 +00:00
* @ param Controller $subject A controller to pull model names off of .
2009-05-24 05:30:04 +00:00
* @ return void
2009-11-14 12:19:25 +00:00
*/
2011-03-26 04:04:25 +00:00
protected function _processController ( $subject ) {
2009-05-24 05:30:04 +00:00
$subject -> constructClasses ();
$models = array ( Inflector :: classify ( $subject -> name ));
if ( ! empty ( $subject -> uses )) {
$models = $subject -> uses ;
}
foreach ( $models as $model ) {
$this -> _processModel ( $subject -> { $model });
}
}
2009-07-24 19:18:37 +00:00
2009-05-24 05:15:31 +00:00
/**
* Add classname to the fixture list .
* Sets the app . or plugin . plugin_name . prefix .
*
2009-12-30 03:50:43 +00:00
* @ param string $name Name of the Model class that a fixture might be required for .
2009-05-24 05:15:31 +00:00
* @ return void
2009-11-14 12:19:25 +00:00
*/
2010-04-05 03:21:28 +00:00
protected function _addFixture ( $name ) {
2009-05-24 05:15:31 +00:00
$parent = get_parent_class ( $name );
$prefix = 'app.' ;
if ( strtolower ( $parent ) != 'appmodel' && strtolower ( substr ( $parent , - 8 )) == 'appmodel' ) {
$pluginName = substr ( $parent , 0 , strlen ( $parent ) - 8 );
$prefix = 'plugin.' . Inflector :: underscore ( $pluginName ) . '.' ;
}
$fixture = $prefix . Inflector :: underscore ( $name );
$this -> _fixtures [ $name ] = $fixture ;
}
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Interact with the user to get additional fixtures they want to use .
*
2009-12-30 03:50:43 +00:00
* @ return array Array of fixtures the user wants to add .
2009-11-14 12:19:25 +00:00
*/
2010-04-05 03:19:38 +00:00
public function getUserFixtures () {
2010-04-15 15:43:39 +00:00
$proceed = $this -> in ( __ ( 'Bake could not detect fixtures, would you like to add some?' ), array ( 'y' , 'n' ), 'n' );
2009-05-25 03:50:21 +00:00
$fixtures = array ();
if ( strtolower ( $proceed ) == 'y' ) {
2010-04-15 15:43:39 +00:00
$fixtureList = $this -> in ( __ ( " Please provide a comma separated list of the fixtures names you'd like to use. \n Example: 'app.comment, app.post, plugin.forums.post' " ));
2009-05-25 03:50:21 +00:00
$fixtureListTrimmed = str_replace ( ' ' , '' , $fixtureList );
$fixtures = explode ( ',' , $fixtureListTrimmed );
}
$this -> _fixtures = array_merge ( $this -> _fixtures , $fixtures );
return $fixtures ;
}
2009-07-24 19:18:37 +00:00
2009-05-25 04:54:23 +00:00
/**
2009-05-26 03:48:41 +00:00
* Is a mock class required for this type of test ?
* Controllers require a mock class .
2009-05-25 04:54:23 +00:00
*
2009-12-30 03:50:43 +00:00
* @ param string $type The type of object tests are being generated for eg . controller .
2009-05-26 03:48:41 +00:00
* @ return boolean
2009-11-14 12:19:25 +00:00
*/
2010-04-05 03:19:38 +00:00
public function hasMockClass ( $type ) {
2009-05-26 03:48:41 +00:00
$type = strtolower ( $type );
return $type == 'controller' ;
2009-05-25 04:54:23 +00:00
}
2009-07-24 19:18:37 +00:00
2009-05-25 04:54:23 +00:00
/**
* Generate a constructor code snippet for the type and classname
*
2009-12-30 03:50:43 +00:00
* @ param string $type The Type of object you are generating tests for eg . controller
* @ param string $className the Classname of the class the test is being generated for .
2009-05-25 04:54:23 +00:00
* @ return string Constructor snippet for the thing you are building .
2009-11-14 12:19:25 +00:00
*/
2010-04-05 03:19:38 +00:00
public function generateConstructor ( $type , $fullClassName ) {
2009-05-25 04:54:23 +00:00
$type = strtolower ( $type );
if ( $type == 'model' ) {
2009-05-25 05:02:59 +00:00
return " ClassRegistry::init(' $fullClassName '); \n " ;
2009-05-25 04:54:23 +00:00
}
if ( $type == 'controller' ) {
2009-08-06 00:45:54 +00:00
$className = substr ( $fullClassName , 0 , strlen ( $fullClassName ) - 10 );
return " new Test $fullClassName (); \n \t \t \$ this-> { $className } ->constructClasses(); \n " ;
2009-05-25 04:54:23 +00:00
}
2010-02-13 19:42:16 +00:00
return " new $fullClassName (); \n " ;
2009-05-25 04:54:23 +00:00
}
2009-07-24 19:18:37 +00:00
2009-06-07 01:03:26 +00:00
/**
2009-12-30 03:50:43 +00:00
* Make the filename for the test case . resolve the suffixes for controllers
2009-06-07 01:03:26 +00:00
* and get the plugin path if needed .
*
2009-12-30 03:50:43 +00:00
* @ param string $type The Type of object you are generating tests for eg . controller
* @ param string $className the Classname of the class the test is being generated for .
* @ return string filename the test should be created on .
2009-11-14 12:19:25 +00:00
*/
2010-04-05 03:19:38 +00:00
public function testCaseFileName ( $type , $className ) {
2010-03-05 02:57:48 +00:00
$path = $this -> getPath ();;
2010-04-22 03:21:25 +00:00
$path .= 'cases' . DS . strtolower ( $type ) . 's' . DS ;
2009-06-07 01:03:26 +00:00
if ( strtolower ( $type ) == 'controller' ) {
$className = $this -> getRealClassName ( $type , $className );
}
return $path . Inflector :: underscore ( $className ) . '.test.php' ;
}
2009-08-17 01:32:10 +00:00
/**
2010-10-13 02:25:04 +00:00
* get the option parser .
2009-08-17 01:32:10 +00:00
*
* @ return void
2009-11-14 12:19:25 +00:00
*/
2010-10-13 02:25:04 +00:00
public function getOptionParser () {
$parser = parent :: getOptionParser ();
return $parser -> description ( __ ( 'Bake test case skeletons for classes.' ))
-> addArgument ( 'type' , array (
'help' => __ ( 'Type of class to bake, can be any of the following: controller, model, helper, component or behavior.' ),
'choices' => array ( 'controller' , 'model' , 'helper' , 'component' , 'behavior' )
)) -> addArgument ( 'name' , array (
'help' => __ ( 'An existing class to bake tests for.' )
)) -> addOption ( 'plugin' , array (
'short' => 'p' ,
'help' => __ ( 'CamelCased name of the plugin to bake tests for.' )
2010-10-13 02:36:51 +00:00
)) -> epilog ( __ ( 'Omitting all arguments and options will enter into an interactive mode.' ));
2009-08-17 01:32:10 +00:00
}
2008-05-30 11:40:08 +00:00
}