'Test Report Title', * 'group' => new Group(['data' => ['lithium\tests\cases\net\http\MediaTest']]), * 'format' => 'html' * ]); * * $report->run(); * * // Get the test stats: * $report->stats(); * * // Get test results: * $report->results * ``` * * You may also choose to filter the results of the test runs to obtain additional information. * For example, say you wish to calculate the cyclomatic complexity of the classes you are testing: * * ``` * $report = new Report([ * 'title' => 'Test Report Title', * 'group' => new Group(['data' => ['lithium\tests\cases\net\http\MediaTest']]), * 'filters' => ['Complexity'] * ]); * * $report->run(); * * // Get test results, including filter results: * $report->results * ``` * * @see lithium\test\Group * @see lithium\test\filter * @see lithium\test\templates */ class Report extends \lithium\core\ObjectDeprecated { /** * Contains an instance of `lithium\test\Group`, which contains all unit tests to be executed * this test run. * * @see lithium\test\Group * @var object */ public $group = null; /** * Title of the group being run. * * @var string */ public $title; /** * Group and filter results. * * @var array */ public $results = ['group' => [], 'filters' => []]; /** * Start and end timers. * * @var array */ public $timer = ['start' => null, 'end' => null]; /** * An array key on fully-namespaced class names of the filter with options to be * applied for the filter as the value * * @var array */ protected $_filters = []; /** * Constructor. * * @param array $config Options array for the test run. Valid options are: * - `'group'`: The test group with items to be run. * - `'filters'`: An array of filters that the test output should be run through. * - `'format'`: The format of the template to use, defaults to `'txt'`. * - `'reporter'`: The reporter to use. * @return void */ public function __construct(array $config = []) { $defaults = [ 'title' => null, 'group' => null, 'filters' => [], 'format' => 'txt', 'reporter' => null ]; parent::__construct($config + $defaults); } /** * Initializer. * * @return void */ protected function _init() { $this->group = $this->_config['group']; $this->title = $this->_config['title'] ?: $this->_config['title']; $this->_filters = $this->filters($this->_config['filters']); } /** * Runs tests. * * @return void */ public function run() { $tests = $this->group->tests(); foreach ($this->filters() as $filter => $options) { $this->results['filters'][$filter] = []; $tests = $filter::apply($this, $tests, $options['apply']) ?: $tests; } $this->results['group'] = $tests->run([ 'reporter' => $this->_config['reporter'] ]); foreach ($this->filters() as $filter => $options) { $this->results['filters'][$filter] = $filter::analyze($this, $options['analyze']); } } /** * Collects Results from the test filters and aggregates them. * * @param string $class Classname of the filter for which to aggregate results. * @param array $results Array of the filter results for * later analysis by the filter itself. * @return void */ public function collect($class, $results) { $this->results['filters'][$class][] = $results; } /** * Return statistics from the test runs. * * @return array */ public function stats() { $results = (array) $this->results['group']; $defaults = [ 'asserts' => 0, 'passes' => [], 'fails' => [], 'exceptions' => [], 'errors' => [], 'skips' => [] ]; $stats = array_reduce($results, function($stats, $result) use ($defaults) { $stats = (array) $stats + $defaults; $result = empty($result[0]) ? [$result] : $result; foreach ($result as $response) { if (empty($response['result'])) { continue; } $result = $response['result']; if (in_array($result, ['fail', 'exception'])) { $response = array_merge( ['class' => 'unknown', 'method' => 'unknown'], $response ); $stats['errors'][] = $response; } unset($response['file'], $response['result']); if (in_array($result, ['pass', 'fail'])) { $stats['asserts']++; } if (in_array($result, ['pass', 'fail', 'exception', 'skip'])) { $stats[Inflector::pluralize($result)][] = $response; } } return $stats; }); $stats = (array) $stats + $defaults; $count = array_map( function($value) { return is_array($value) ? count($value) : $value; }, $stats ); $success = $count['passes'] === $count['asserts'] && $count['errors'] === 0; return compact('stats', 'count', 'success'); } /** * Renders the test output (e.g. layouts and filter templates). * * @param string $template name of the template (i.e. `'layout'`). * @param string|array $data array from `_data()` method. * @return string * @filter */ public function render($template, $data = []) { $config = $this->_config; if ($template === 'stats' && !$data) { $data = $this->stats(); } $template = Libraries::locate('test.templates', $template, [ 'filter' => false, 'type' => 'file', 'suffix' => ".{$config['format']}.php" ]); if ($template === null) { $message = "Templates for format `{$config['format']}` not found in `test/templates`."; throw new TemplateException($message); } $params = compact('template', 'data', 'config'); return Filters::run(__CLASS__, __FUNCTION__, $params, function($params) { extract($params['data']); ob_start(); include $params['template']; return ob_get_clean(); }); } /** * Getter/setter for report test filters. * * @param array $filters A set of filters, mapping the filter class names, to their * corresponding array of options. When not provided, simply returns current * set of filters. * @return array The current set of filters. */ public function filters(array $filters = []) { foreach ($filters as $filter => $options) { if (!$class = Libraries::locate('test.filter', $filter)) { throw new ClassNotFoundException("`{$class}` is not a valid test filter."); } $this->_filters[$class] = $options + [ 'name' => strtolower(join('', array_slice(explode("\\", $class), -1))), 'apply' => [], 'analyze' => [] ]; } return $this->_filters; } } ?>