PageRenderTime 325ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/test/Report.php

http://github.com/UnionOfRAD/lithium
PHP | 274 lines | 120 code | 23 blank | 131 comment | 10 complexity | c67165eabfd8b6ca67ec3be59e5ef3e4 MD5 | raw file
  1. <?php
  2. /**
  3. * li₃: the most RAD framework for PHP (http://li3.me)
  4. *
  5. * Copyright 2009, Union of RAD. All rights reserved. This source
  6. * code is distributed under the terms of the BSD 3-Clause License.
  7. * The full license text can be found in the LICENSE.txt file.
  8. */
  9. namespace lithium\test;
  10. use lithium\aop\Filters;
  11. use lithium\core\Libraries;
  12. use lithium\util\Inflector;
  13. use lithium\core\ClassNotFoundException;
  14. use lithium\template\TemplateException;
  15. /**
  16. * This `Report` object aggregates tests in a group and allows you to run said tests to
  17. * obtain the results and stats (passes, fails, exceptions, skips) of the test run.
  18. *
  19. * While Lithium already comes with a text-based as well as web-based test interface, you
  20. * may use or extend the `Report` class to create your own test report functionality. In
  21. * addition, you can also create your own custom templates for displaying results in a different
  22. * format, such as json.
  23. *
  24. * Example usage, for built-in HTML format:
  25. *
  26. * ```
  27. * $report = new Report([
  28. * 'title' => 'Test Report Title',
  29. * 'group' => new Group(['data' => ['lithium\tests\cases\net\http\MediaTest']]),
  30. * 'format' => 'html'
  31. * ]);
  32. *
  33. * $report->run();
  34. *
  35. * // Get the test stats:
  36. * $report->stats();
  37. *
  38. * // Get test results:
  39. * $report->results
  40. * ```
  41. *
  42. * You may also choose to filter the results of the test runs to obtain additional information.
  43. * For example, say you wish to calculate the cyclomatic complexity of the classes you are testing:
  44. *
  45. * ```
  46. * $report = new Report([
  47. * 'title' => 'Test Report Title',
  48. * 'group' => new Group(['data' => ['lithium\tests\cases\net\http\MediaTest']]),
  49. * 'filters' => ['Complexity']
  50. * ]);
  51. *
  52. * $report->run();
  53. *
  54. * // Get test results, including filter results:
  55. * $report->results
  56. * ```
  57. *
  58. * @see lithium\test\Group
  59. * @see lithium\test\filter
  60. * @see lithium\test\templates
  61. */
  62. class Report extends \lithium\core\ObjectDeprecated {
  63. /**
  64. * Contains an instance of `lithium\test\Group`, which contains all unit tests to be executed
  65. * this test run.
  66. *
  67. * @see lithium\test\Group
  68. * @var object
  69. */
  70. public $group = null;
  71. /**
  72. * Title of the group being run.
  73. *
  74. * @var string
  75. */
  76. public $title;
  77. /**
  78. * Group and filter results.
  79. *
  80. * @var array
  81. */
  82. public $results = ['group' => [], 'filters' => []];
  83. /**
  84. * Start and end timers.
  85. *
  86. * @var array
  87. */
  88. public $timer = ['start' => null, 'end' => null];
  89. /**
  90. * An array key on fully-namespaced class names of the filter with options to be
  91. * applied for the filter as the value
  92. *
  93. * @var array
  94. */
  95. protected $_filters = [];
  96. /**
  97. * Constructor.
  98. *
  99. * @param array $config Options array for the test run. Valid options are:
  100. * - `'group'`: The test group with items to be run.
  101. * - `'filters'`: An array of filters that the test output should be run through.
  102. * - `'format'`: The format of the template to use, defaults to `'txt'`.
  103. * - `'reporter'`: The reporter to use.
  104. * @return void
  105. */
  106. public function __construct(array $config = []) {
  107. $defaults = [
  108. 'title' => null,
  109. 'group' => null,
  110. 'filters' => [],
  111. 'format' => 'txt',
  112. 'reporter' => null
  113. ];
  114. parent::__construct($config + $defaults);
  115. }
  116. /**
  117. * Initializer.
  118. *
  119. * @return void
  120. */
  121. protected function _init() {
  122. $this->group = $this->_config['group'];
  123. $this->title = $this->_config['title'] ?: $this->_config['title'];
  124. $this->_filters = $this->filters($this->_config['filters']);
  125. }
  126. /**
  127. * Runs tests.
  128. *
  129. * @return void
  130. */
  131. public function run() {
  132. $tests = $this->group->tests();
  133. foreach ($this->filters() as $filter => $options) {
  134. $this->results['filters'][$filter] = [];
  135. $tests = $filter::apply($this, $tests, $options['apply']) ?: $tests;
  136. }
  137. $this->results['group'] = $tests->run([
  138. 'reporter' => $this->_config['reporter']
  139. ]);
  140. foreach ($this->filters() as $filter => $options) {
  141. $this->results['filters'][$filter] = $filter::analyze($this, $options['analyze']);
  142. }
  143. }
  144. /**
  145. * Collects Results from the test filters and aggregates them.
  146. *
  147. * @param string $class Classname of the filter for which to aggregate results.
  148. * @param array $results Array of the filter results for
  149. * later analysis by the filter itself.
  150. * @return void
  151. */
  152. public function collect($class, $results) {
  153. $this->results['filters'][$class][] = $results;
  154. }
  155. /**
  156. * Return statistics from the test runs.
  157. *
  158. * @return array
  159. */
  160. public function stats() {
  161. $results = (array) $this->results['group'];
  162. $defaults = [
  163. 'asserts' => 0,
  164. 'passes' => [],
  165. 'fails' => [],
  166. 'exceptions' => [],
  167. 'errors' => [],
  168. 'skips' => []
  169. ];
  170. $stats = array_reduce($results, function($stats, $result) use ($defaults) {
  171. $stats = (array) $stats + $defaults;
  172. $result = empty($result[0]) ? [$result] : $result;
  173. foreach ($result as $response) {
  174. if (empty($response['result'])) {
  175. continue;
  176. }
  177. $result = $response['result'];
  178. if (in_array($result, ['fail', 'exception'])) {
  179. $response = array_merge(
  180. ['class' => 'unknown', 'method' => 'unknown'], $response
  181. );
  182. $stats['errors'][] = $response;
  183. }
  184. unset($response['file'], $response['result']);
  185. if (in_array($result, ['pass', 'fail'])) {
  186. $stats['asserts']++;
  187. }
  188. if (in_array($result, ['pass', 'fail', 'exception', 'skip'])) {
  189. $stats[Inflector::pluralize($result)][] = $response;
  190. }
  191. }
  192. return $stats;
  193. });
  194. $stats = (array) $stats + $defaults;
  195. $count = array_map(
  196. function($value) { return is_array($value) ? count($value) : $value; }, $stats
  197. );
  198. $success = $count['passes'] === $count['asserts'] && $count['errors'] === 0;
  199. return compact('stats', 'count', 'success');
  200. }
  201. /**
  202. * Renders the test output (e.g. layouts and filter templates).
  203. *
  204. * @param string $template name of the template (i.e. `'layout'`).
  205. * @param string|array $data array from `_data()` method.
  206. * @return string
  207. * @filter
  208. */
  209. public function render($template, $data = []) {
  210. $config = $this->_config;
  211. if ($template === 'stats' && !$data) {
  212. $data = $this->stats();
  213. }
  214. $template = Libraries::locate('test.templates', $template, [
  215. 'filter' => false, 'type' => 'file', 'suffix' => ".{$config['format']}.php"
  216. ]);
  217. if ($template === null) {
  218. $message = "Templates for format `{$config['format']}` not found in `test/templates`.";
  219. throw new TemplateException($message);
  220. }
  221. $params = compact('template', 'data', 'config');
  222. return Filters::run(__CLASS__, __FUNCTION__, $params, function($params) {
  223. extract($params['data']);
  224. ob_start();
  225. include $params['template'];
  226. return ob_get_clean();
  227. });
  228. }
  229. /**
  230. * Getter/setter for report test filters.
  231. *
  232. * @param array $filters A set of filters, mapping the filter class names, to their
  233. * corresponding array of options. When not provided, simply returns current
  234. * set of filters.
  235. * @return array The current set of filters.
  236. */
  237. public function filters(array $filters = []) {
  238. foreach ($filters as $filter => $options) {
  239. if (!$class = Libraries::locate('test.filter', $filter)) {
  240. throw new ClassNotFoundException("`{$class}` is not a valid test filter.");
  241. }
  242. $this->_filters[$class] = $options + [
  243. 'name' => strtolower(join('', array_slice(explode("\\", $class), -1))),
  244. 'apply' => [],
  245. 'analyze' => []
  246. ];
  247. }
  248. return $this->_filters;
  249. }
  250. }
  251. ?>