5 * Copyright (c) 2002-2011, Sebastian Bergmann <sebastian@phpunit.de>.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
20 * * Neither the name of Sebastian Bergmann nor the names of his
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
38 * @subpackage Framework
39 * @author Sebastian Bergmann <sebastian@phpunit.de>
40 * @copyright 2002-2011 Sebastian Bergmann <sebastian@phpunit.de>
41 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
42 * @link http://www.phpunit.de/
43 * @since File available since Release 2.0.0
46 require_once 'PHP/CodeCoverage.php';
49 * A TestSuite is a composite of Tests. It runs a collection of test cases.
51 * Here is an example using the dynamic test definition.
55 * $suite = new PHPUnit_Framework_TestSuite;
56 * $suite->addTest(new MathTest('testPass'));
60 * Alternatively, a TestSuite can extract the tests to be run automatically.
61 * To do so you pass a ReflectionClass instance for your
62 * PHPUnit_Framework_TestCase class to the PHPUnit_Framework_TestSuite
67 * $suite = new PHPUnit_Framework_TestSuite(
68 * new ReflectionClass('MathTest')
73 * This constructor creates a suite with all the methods starting with
74 * "test" that take no arguments.
77 * @subpackage Framework
78 * @author Sebastian Bergmann <sebastian@phpunit.de>
79 * @copyright 2002-2011 Sebastian Bergmann <sebastian@phpunit.de>
80 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
81 * @version Release: 3.5.14
82 * @link http://www.phpunit.de/
83 * @since Class available since Release 2.0.0
85 class PHPUnit_Framework_TestSuite implements PHPUnit_Framework_Test, PHPUnit_Framework_SelfDescribing, IteratorAggregate
88 * Enable or disable the backup and restoration of the $GLOBALS array.
92 protected $backupGlobals = NULL;
95 * Enable or disable the backup and restoration of static attributes.
99 protected $backupStaticAttributes = NULL;
102 * The name of the test suite.
106 protected $name = '';
109 * The test groups of the test suite.
113 protected $groups = array();
116 * The tests in the test suite.
120 protected $tests = array();
123 * The number of tests in the test suite.
127 protected $numTests = -1;
132 protected $testCase = FALSE;
135 * Constructs a new TestSuite:
137 * - PHPUnit_Framework_TestSuite() constructs an empty TestSuite.
139 * - PHPUnit_Framework_TestSuite(ReflectionClass) constructs a
140 * TestSuite from the given class.
142 * - PHPUnit_Framework_TestSuite(ReflectionClass, String)
143 * constructs a TestSuite from the given class with the given
146 * - PHPUnit_Framework_TestSuite(String) either constructs a
147 * TestSuite from the given class (if the passed string is the
148 * name of an existing class) or constructs an empty TestSuite
149 * with the given name.
151 * @param mixed $theClass
152 * @param string $name
153 * @throws InvalidArgumentException
155 public function __construct($theClass = '', $name = '')
157 $argumentsValid = FALSE;
159 if (is_object($theClass) &&
160 $theClass instanceof ReflectionClass) {
161 $argumentsValid = TRUE;
164 else if (is_string($theClass) &&
166 class_exists($theClass, FALSE)) {
167 $argumentsValid = TRUE;
173 $theClass = new ReflectionClass($theClass);
176 else if (is_string($theClass)) {
177 $this->setName($theClass);
181 if (!$argumentsValid) {
182 throw new InvalidArgumentException;
185 if (!$theClass->isSubclassOf('PHPUnit_Framework_TestCase')) {
186 throw new InvalidArgumentException(
187 'Class does not extend PHPUnit_Framework_TestCase.'
191 $filename = $theClass->getFilename();
193 if (strpos($filename, 'eval()') === FALSE) {
194 PHP_CodeCoverage::getInstance()->filter()->addFileToBlacklist(
195 realpath($filename), 'TESTS'
200 $this->setName($name);
202 $this->setName($theClass->getName());
205 $constructor = $theClass->getConstructor();
207 if ($constructor !== NULL &&
208 !$constructor->isPublic()) {
212 'Class "%s" has no public constructor.',
222 foreach ($theClass->getMethods() as $method) {
223 if (strpos($method->getDeclaringClass()->getName(), 'PHPUnit_') !== 0) {
224 $this->addTestMethod($theClass, $method);
228 if (empty($this->tests)) {
232 'No tests found in class "%s".',
240 $this->testCase = TRUE;
244 * Returns a string representation of the test suite.
248 public function toString()
250 return $this->getName();
254 * Adds a test to the suite.
256 * @param PHPUnit_Framework_Test $test
257 * @param array $groups
259 public function addTest(PHPUnit_Framework_Test $test, $groups = array())
261 $class = new ReflectionClass($test);
263 if (!$class->isAbstract()) {
264 $this->tests[] = $test;
265 $this->numTests = -1;
267 if ($test instanceof PHPUnit_Framework_TestSuite &&
269 $groups = $test->getGroups();
272 if (empty($groups)) {
273 $groups = array('__nogroup__');
276 foreach ($groups as $group) {
277 if (!isset($this->groups[$group])) {
278 $this->groups[$group] = array($test);
280 $this->groups[$group][] = $test;
287 * Adds the tests from the given class to the suite.
289 * @param mixed $testClass
290 * @throws InvalidArgumentException
292 public function addTestSuite($testClass)
294 if (is_string($testClass) && class_exists($testClass)) {
295 $testClass = new ReflectionClass($testClass);
298 if (!is_object($testClass)) {
299 throw PHPUnit_Util_InvalidArgumentHelper::factory(
300 1, 'class name or object'
304 if ($testClass instanceof PHPUnit_Framework_TestSuite) {
305 $this->addTest($testClass);
308 else if ($testClass instanceof ReflectionClass) {
309 $suiteMethod = FALSE;
311 if (!$testClass->isAbstract()) {
312 if ($testClass->hasMethod(PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME)) {
313 $method = $testClass->getMethod(
314 PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME
317 if ($method->isStatic()) {
319 $method->invoke(NULL, $testClass->getName())
327 if (!$suiteMethod && !$testClass->isAbstract()) {
328 $this->addTest(new PHPUnit_Framework_TestSuite($testClass));
333 throw new InvalidArgumentException;
338 * Wraps both <code>addTest()</code> and <code>addTestSuite</code>
339 * as well as the separate import statements for the user's convenience.
341 * If the named file cannot be read or there are no new tests that can be
342 * added, a <code>PHPUnit_Framework_Warning</code> will be created instead,
343 * leaving the current test run untouched.
345 * @param string $filename
346 * @param boolean $syntaxCheck
347 * @param array $phptOptions Array with ini settings for the php instance
348 * run, key being the name if the setting,
349 * value the ini value.
350 * @throws InvalidArgumentException
351 * @since Method available since Release 2.3.0
352 * @author Stefano F. Rausch <stefano@rausch-e.net>
354 public function addTestFile($filename, $syntaxCheck = FALSE, $phptOptions = array())
356 if (!is_string($filename)) {
357 throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
360 if (file_exists($filename) && substr($filename, -5) == '.phpt') {
362 new PHPUnit_Extensions_PhptTestCase($filename, $phptOptions)
368 PHPUnit_Util_Class::collectStart();
369 $filename = PHPUnit_Util_Fileloader::checkAndLoad($filename, $syntaxCheck);
370 $newClasses = PHPUnit_Util_Class::collectEnd();
371 $baseName = str_replace('.php', '', basename($filename));
373 foreach ($newClasses as $className) {
374 if (substr($className, 0 - strlen($baseName)) == $baseName) {
375 $class = new ReflectionClass($className);
377 if ($class->getFileName() == $filename) {
378 $newClasses = array($className);
386 foreach ($newClasses as $className) {
387 $class = new ReflectionClass($className);
389 if (!$class->isAbstract()) {
390 if ($class->hasMethod(PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME)) {
391 $method = $class->getMethod(
392 PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME
395 if ($method->isStatic()) {
396 $this->addTest($method->invoke(NULL, $className));
402 else if ($class->implementsInterface('PHPUnit_Framework_Test')) {
403 $this->addTestSuite($class);
410 $this->numTests = -1;
414 * Wrapper for addTestFile() that adds multiple test files.
416 * @param array|Iterator $filenames
417 * @throws InvalidArgumentException
418 * @since Method available since Release 2.3.0
420 public function addTestFiles($filenames, $syntaxCheck = FALSE)
422 if (!(is_array($filenames) ||
423 (is_object($filenames) && $filenames instanceof Iterator))) {
424 throw PHPUnit_Util_InvalidArgumentHelper::factory(
425 1, 'array or iterator'
429 foreach ($filenames as $filename) {
430 $this->addTestFile((string)$filename, $syntaxCheck);
435 * Counts the number of test cases that will be run by this test.
439 public function count()
441 if ($this->numTests > -1) {
442 return $this->numTests;
447 foreach ($this->tests as $test) {
448 $this->numTests += count($test);
451 return $this->numTests;
455 * @param ReflectionClass $theClass
456 * @param string $name
457 * @return PHPUnit_Framework_Test
458 * @throws RuntimeException
460 public static function createTest(ReflectionClass $theClass, $name)
462 $className = $theClass->getName();
464 if (!$theClass->isInstantiable()) {
465 return self::warning(
466 sprintf('Cannot instantiate class "%s".', $className)
470 $backupSettings = PHPUnit_Util_Test::getBackupSettings(
473 $preserveGlobalState = PHPUnit_Util_Test::getPreserveGlobalStateSettings(
476 $runTestInSeparateProcess = PHPUnit_Util_Test::getProcessIsolationSettings(
480 $constructor = $theClass->getConstructor();
482 if ($constructor !== NULL) {
483 $parameters = $constructor->getParameters();
485 // TestCase() or TestCase($name)
486 if (count($parameters) < 2) {
487 $test = new $className;
490 // TestCase($name, $data)
493 $data = PHPUnit_Util_Test::getProvidedData(
498 catch (Exception $e) {
500 'The data provider specified for %s::%s is invalid.',
505 $_message = $e->getMessage();
507 if (!empty($_message)) {
508 $message .= "\n" . $_message;
511 $data = self::warning($message);
514 // Test method with @dataProvider.
516 $test = new PHPUnit_Framework_TestSuite_DataProvider(
517 $className . '::' . $name
521 $data = self::warning(
523 'No tests found in suite "%s".',
529 if ($data instanceof PHPUnit_Framework_Warning) {
530 $test->addTest($data);
534 $groups = PHPUnit_Util_Test::getGroups($className, $name);
536 foreach ($data as $_dataName => $_data) {
537 $_test = new $className($name, $_data, $_dataName);
539 if ($runTestInSeparateProcess) {
540 $_test->setRunTestInSeparateProcess(TRUE);
542 if ($preserveGlobalState !== NULL) {
543 $_test->setPreserveGlobalState($preserveGlobalState);
547 if ($backupSettings['backupGlobals'] !== NULL) {
548 $_test->setBackupGlobals(
549 $backupSettings['backupGlobals']
553 if ($backupSettings['backupStaticAttributes'] !== NULL) {
554 $_test->setBackupStaticAttributes(
555 $backupSettings['backupStaticAttributes']
559 $test->addTest($_test, $groups);
565 $test = new $className;
571 throw new RuntimeException('No valid test provided.');
574 if ($test instanceof PHPUnit_Framework_TestCase) {
575 $test->setName($name);
577 if ($runTestInSeparateProcess) {
578 $test->setRunTestInSeparateProcess(TRUE);
580 if ($preserveGlobalState !== NULL) {
581 $test->setPreserveGlobalState($preserveGlobalState);
585 if ($backupSettings['backupGlobals'] !== NULL) {
586 $test->setBackupGlobals($backupSettings['backupGlobals']);
589 if ($backupSettings['backupStaticAttributes'] !== NULL) {
590 $test->setBackupStaticAttributes(
591 $backupSettings['backupStaticAttributes']
600 * Creates a default TestResult object.
602 * @return PHPUnit_Framework_TestResult
604 protected function createResult()
606 return new PHPUnit_Framework_TestResult;
610 * Returns the name of the suite.
614 public function getName()
620 * Returns the test groups of the suite.
623 * @since Method available since Release 3.2.0
625 public function getGroups()
627 return array_keys($this->groups);
631 * Runs the tests and collects their result in a TestResult.
633 * @param PHPUnit_Framework_TestResult $result
634 * @param mixed $filter
635 * @param array $groups
636 * @param array $excludeGroups
637 * @param boolean $processIsolation
638 * @return PHPUnit_Framework_TestResult
639 * @throws InvalidArgumentException
641 public function run(PHPUnit_Framework_TestResult $result = NULL, $filter = FALSE, array $groups = array(), array $excludeGroups = array(), $processIsolation = FALSE)
643 if ($result === NULL) {
644 $result = $this->createResult();
647 $result->startTestSuite($this);
652 if ($this->testCase &&
653 method_exists($this->name, 'setUpBeforeClass')) {
654 call_user_func(array($this->name, 'setUpBeforeClass'));
658 catch (PHPUnit_Framework_SkippedTestSuiteError $e) {
659 $numTests = count($this);
661 for ($i = 0; $i < $numTests; $i++) {
662 $result->addFailure($this, $e, 0);
668 if (empty($groups)) {
669 $tests = $this->tests;
671 $tests = new SplObjectStorage;
673 foreach ($groups as $group) {
674 if (isset($this->groups[$group])) {
675 foreach ($this->groups[$group] as $test) {
676 $tests->attach($test);
682 foreach ($tests as $test) {
683 if ($result->shouldStop()) {
687 if ($test instanceof PHPUnit_Framework_TestSuite) {
688 $test->setBackupGlobals($this->backupGlobals);
689 $test->setBackupStaticAttributes($this->backupStaticAttributes);
692 $result, $filter, $groups, $excludeGroups, $processIsolation
697 if ($filter !== FALSE ) {
698 $tmp = PHPUnit_Util_Test::describe($test, FALSE);
701 $name = join('::', $tmp);
706 if (preg_match($filter, $name) == 0) {
711 if ($runTest && !empty($excludeGroups)) {
712 foreach ($this->groups as $_group => $_tests) {
713 if (in_array($_group, $excludeGroups)) {
714 foreach ($_tests as $_test) {
715 if ($test === $_test) {
725 if ($test instanceof PHPUnit_Framework_TestCase) {
726 $test->setBackupGlobals($this->backupGlobals);
727 $test->setBackupStaticAttributes(
728 $this->backupStaticAttributes
730 $test->setRunTestInSeparateProcess($processIsolation);
733 $this->runTest($test, $result);
738 if ($this->testCase &&
739 method_exists($this->name, 'tearDownAfterClass')) {
740 call_user_func(array($this->name, 'tearDownAfterClass'));
744 $result->endTestSuite($this);
752 * @param PHPUnit_Framework_Test $test
753 * @param PHPUnit_Framework_TestResult $testResult
755 public function runTest(PHPUnit_Framework_Test $test, PHPUnit_Framework_TestResult $result)
761 * Sets the name of the suite.
765 public function setName($name)
771 * Returns the test at the given index.
774 * @return PHPUnit_Framework_Test
776 public function testAt($index)
778 if (isset($this->tests[$index])) {
779 return $this->tests[$index];
786 * Returns the tests as an enumeration.
790 public function tests()
796 * Mark the test suite as skipped.
798 * @param string $message
799 * @throws PHPUnit_Framework_SkippedTestSuiteError
800 * @since Method available since Release 3.0.0
802 public function markTestSuiteSkipped($message = '')
804 throw new PHPUnit_Framework_SkippedTestSuiteError($message);
808 * @param ReflectionClass $class
809 * @param ReflectionMethod $method
811 protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method)
813 $name = $method->getName();
815 if ($this->isPublicTestMethod($method)) {
816 $test = self::createTest($class, $name);
818 if ($test instanceof PHPUnit_Framework_TestCase ||
819 $test instanceof PHPUnit_Framework_TestSuite_DataProvider) {
820 $test->setDependencies(
821 PHPUnit_Util_Test::getDependencies($class->getName(), $name)
825 $this->addTest($test, PHPUnit_Util_Test::getGroups(
826 $class->getName(), $name)
830 else if ($this->isTestMethod($method)) {
834 'Test method "%s" in test class "%s" is not public.',
844 * @param ReflectionMethod $method
847 public static function isPublicTestMethod(ReflectionMethod $method)
849 return (self::isTestMethod($method) && $method->isPublic());
853 * @param ReflectionMethod $method
856 public static function isTestMethod(ReflectionMethod $method)
858 if (strpos($method->name, 'test') === 0) {
862 // @scenario on TestCase::testMethod()
863 // @test on TestCase::testMethod()
864 return strpos($method->getDocComment(), '@test') !== FALSE ||
865 strpos($method->getDocComment(), '@scenario') !== FALSE;
869 * @param string $message
870 * @return PHPUnit_Framework_Warning
872 protected static function warning($message)
874 return new PHPUnit_Framework_Warning($message);
878 * @param boolean $backupGlobals
879 * @since Method available since Release 3.3.0
881 public function setBackupGlobals($backupGlobals)
883 if (is_null($this->backupGlobals) && is_bool($backupGlobals)) {
884 $this->backupGlobals = $backupGlobals;
889 * @param boolean $backupStaticAttributes
890 * @since Method available since Release 3.4.0
892 public function setBackupStaticAttributes($backupStaticAttributes)
894 if (is_null($this->backupStaticAttributes) &&
895 is_bool($backupStaticAttributes)) {
896 $this->backupStaticAttributes = $backupStaticAttributes;
901 * Returns an iterator for this test suite.
903 * @return RecursiveIteratorIterator
904 * @since Method available since Release 3.1.0
906 public function getIterator()
908 return new RecursiveIteratorIterator(
909 new PHPUnit_Util_TestSuiteIterator($this)
914 * Template Method that is called before the tests
915 * of this test suite are run.
917 * @since Method available since Release 3.1.0
919 protected function setUp()
924 * Template Method that is called after the tests
925 * of this test suite have finished running.
927 * @since Method available since Release 3.1.0
929 protected function tearDown()