You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

275 lines
7.9 KiB

  1. <?php
  2. /**
  3. * 单元测试骨架代码自动生成脚本
  4. * 主要是针对当前项目系列生成相应的单元测试代码,提高开发效率
  5. *
  6. * 用法:
  7. * Usage: php ./build_phpunit_test_tpl.php <file_path> <class_name> [bootstrap] [author = dogstar]
  8. *
  9. * 1、针对全部public的函数进行单元测试
  10. * 2、可根据@testcase注释自动生成测试用例
  11. *
  12. * 备注:另可使用phpunit-skelgen进行骨架代码生成
  13. *
  14. * @author: dogstar 20150119
  15. * @version: 4.0.2
  16. */
  17. if ($argc < 3) {
  18. echo "\n";
  19. echo colorfulString("Usage:\n", 'WARNING');
  20. echo " php $argv[0] <file_path> <class_name> [bootstrap] [author]\n";
  21. echo "\n";
  22. echo colorfulString("Options:\n", 'WARNING');
  23. echo colorfulString(' file_path', 'NOTE'), " Require. Path to the PHP source code file\n";
  24. echo colorfulString(' class_name', 'NOTE'), " Require. The class name need to be tested\n";
  25. echo colorfulString(' bootstrap', 'NOTE'), " NOT require. Path to the bootsrap file, usually is test_env.php\n";
  26. echo colorfulString(' author', 'NOTE'), " NOT require. Your great name here, default is dogstar\n";
  27. echo "\n";
  28. echo colorfulString("Demo:\n", 'WARNING');
  29. echo " php ./build_phpunit_test_tpl.php ./Demo.php Demo > Demo_Test.php\n";
  30. echo "\n";
  31. echo colorfulString("Tips:\n", 'WARNING');
  32. echo " This will output the code directly, you can save them to test file like with _Test.php suffix.\n";
  33. echo "\n";
  34. die();
  35. }
  36. $filePath = $argv[1];
  37. $className = $argv[2];
  38. $bootstrap = isset($argv[3]) ? $argv[3] : null;
  39. $author = isset($argv[4]) ? $argv[4] : 'dogstar';
  40. if (!empty($bootstrap)) {
  41. require $bootstrap;
  42. }
  43. require $filePath;
  44. if (!class_exists($className)) {
  45. echo colorfulString("Error: cannot find class($className). \n\n", 'FAILURE');
  46. die();
  47. }
  48. $reflector = new ReflectionClass($className);
  49. $methods = $reflector->getMethods(ReflectionMethod::IS_PUBLIC);
  50. date_default_timezone_set('Asia/Shanghai');
  51. $objName = lcfirst(str_replace('_', '', $className));
  52. /** ------------------- 生成通用的单元测试代码 ------------------ **/
  53. $code = "<?php
  54. /**
  55. * PhpUnderControl_" . str_replace('_', '', $className) . "_Test
  56. *
  57. * 针对 $filePath $className 类的PHPUnit单元测试
  58. *
  59. * @author: $author " . date('Ymd') . "
  60. */
  61. ";
  62. if (file_exists(dirname(__FILE__) . '/test_env.php')) {
  63. $code .= "require_once dirname(__FILE__) . '/test_env.php';
  64. ";
  65. } else {
  66. $code .= "//require_once dirname(__FILE__) . '/test_env.php';
  67. ";
  68. }
  69. $initWay = "new $className()";
  70. if (method_exists($className, '__construct')) {
  71. $constructMethod = new ReflectionMethod($className, '__construct');
  72. if (!$constructMethod->isPublic()) {
  73. if (is_callable(array($className, 'getInstance'))) {
  74. $initWay = "$className::getInstance()";
  75. } else if(is_callable(array($className, 'newInstance'))) {
  76. $initWay = "$className::newInstance()";
  77. } else {
  78. $initWay = 'NULL';
  79. }
  80. }
  81. }
  82. $code .= "
  83. if (!class_exists('$className')) {
  84. require dirname(__FILE__) . '/$filePath';
  85. }
  86. class PhpUnderControl_" . str_replace('_', '', $className) . "_Test extends PHPUnit_Framework_TestCase
  87. {
  88. public \$$objName;
  89. protected function setUp()
  90. {
  91. parent::setUp();
  92. \$this->$objName = $initWay;
  93. }
  94. protected function tearDown()
  95. {
  96. }
  97. ";
  98. foreach ($methods as $method) {
  99. if($method->class != $className) continue;
  100. $fun = $method->name;
  101. $Fun = ucfirst($fun);
  102. if (strlen($Fun) > 2 && substr($Fun, 0, 2) == '__') continue;
  103. $rMethod = new ReflectionMethod($className, $method->name);
  104. $params = $rMethod->getParameters();
  105. $isStatic = $rMethod->isStatic();
  106. $isConstructor = $rMethod->isConstructor();
  107. if($isConstructor) continue;
  108. $initParamStr = '';
  109. $callParamStr = '';
  110. foreach ($params as $param) {
  111. $default = '';
  112. $rp = new ReflectionParameter(array($className, $fun), $param->name);
  113. if ($rp->isOptional()) {
  114. $default = $rp->getDefaultValue();
  115. }
  116. if (is_string($default)) {
  117. $default = "'$default'";
  118. } else if (is_array($default)) {
  119. $default = var_export($default, true);
  120. } else if (is_bool($default)) {
  121. $default = $default ? 'true' : 'false';
  122. } else if ($default === null) {
  123. $default = 'null';
  124. } else {
  125. $default = "''";
  126. }
  127. $initParamStr .= "
  128. \$" . $param->name . " = $default;";
  129. $callParamStr .= '$' . $param->name . ', ';
  130. }
  131. $callParamStr = empty($callParamStr) ? $callParamStr : substr($callParamStr, 0, -2);
  132. /** ------------------- 根据@return对结果类型的简单断言 ------------------ **/
  133. $returnAssert = '';
  134. $docComment = $rMethod->getDocComment();
  135. $docCommentArr = explode("\n", $docComment);
  136. foreach ($docCommentArr as $comment) {
  137. if (strpos($comment, '@return') == false) {
  138. continue;
  139. }
  140. $returnCommentArr = explode(' ', strrchr($comment, '@return'));
  141. if (count($returnCommentArr) >= 2) {
  142. switch (strtolower($returnCommentArr[1])) {
  143. case 'bool':
  144. case 'boolean':
  145. $returnAssert = '$this->assertTrue(is_bool($rs));';
  146. break;
  147. case 'int':
  148. $returnAssert = '$this->assertTrue(is_int($rs));';
  149. break;
  150. case 'integer':
  151. $returnAssert = '$this->assertTrue(is_integer($rs));';
  152. break;
  153. case 'string':
  154. $returnAssert = '$this->assertTrue(is_string($rs));';
  155. break;
  156. case 'object':
  157. $returnAssert = '$this->assertTrue(is_object($rs));';
  158. break;
  159. case 'array':
  160. $returnAssert = '$this->assertTrue(is_array($rs));';
  161. break;
  162. case 'float':
  163. $returnAssert = '$this->assertTrue(is_float($rs));';
  164. break;
  165. }
  166. break;
  167. }
  168. }
  169. /** ------------------- 基本的单元测试代码生成 ------------------ **/
  170. $code .= "
  171. /**
  172. * @group test$Fun
  173. */
  174. public function test$Fun()
  175. {"
  176. . (empty($initParamStr) ? '' : "$initParamStr\n")
  177. . "\n "
  178. . ($isStatic ? "\$rs = $className::$fun($callParamStr);" : "\$rs = \$this->$objName->$fun($callParamStr);")
  179. . (empty($returnAssert) ? '' : "\n\n " . $returnAssert . "\n")
  180. . "
  181. }
  182. ";
  183. /** ------------------- 根据@testcase 生成测试代码 ------------------ **/
  184. $caseNum = 0;
  185. foreach ($docCommentArr as $comment) {
  186. if (strpos($comment, '@testcase') == false) {
  187. continue;
  188. }
  189. $returnCommentArr = explode(' ', strrchr($comment, '@testcase'));
  190. if (count($returnCommentArr) > 1) {
  191. $expRs = $returnCommentArr[1];
  192. //去掉@testcase和期望的结果
  193. array_shift($returnCommentArr);
  194. array_shift($returnCommentArr);
  195. $callParamStrInCase = !empty($returnCommentArr) ? implode(' ', $returnCommentArr) : '';
  196. $code .= "
  197. /**
  198. * @group test$Fun
  199. */
  200. public function test{$Fun}Case{$caseNum}()
  201. {"
  202. . "\n "
  203. . ($isStatic ? "\$rs = $className::$fun($callParamStrInCase);" : "\$rs = \$this->$objName->$fun($callParamStrInCase);")
  204. . "\n\n \$this->assertEquals({$expRs}, \$rs);"
  205. . "
  206. }
  207. ";
  208. $caseNum ++;
  209. }
  210. }
  211. }
  212. $code .= "
  213. }";
  214. echo $code;
  215. echo "\n";
  216. function colorfulString($text, $type = NULL) {
  217. $colors = array(
  218. 'WARNING' => '1;33',
  219. 'NOTE' => '1;36',
  220. 'SUCCESS' => '1;32',
  221. 'FAILURE' => '1;35',
  222. );
  223. if (empty($type) || !isset($colors[$type])){
  224. return $text;
  225. }
  226. return "\033[" . $colors[$type] . "m" . $text . "\033[0m";
  227. }