diff --git a/_config/dev.yml b/_config/dev.yml index d9fd34d6b..4cea760de 100644 --- a/_config/dev.yml +++ b/_config/dev.yml @@ -7,11 +7,6 @@ DevelopmentAdmin: controller: 'DevBuildController' links: build: 'Build/rebuild this environment. Call this whenever you have updated your project sources' - tests: - controller: 'TestRunner' - links: - tests: 'See a list of unit tests to run' - 'tests/all': 'Run all tests' tasks: controller: 'TaskRunner' links: diff --git a/dev/CliTestReporter.php b/dev/CliTestReporter.php index 5bf18bed8..540e13332 100644 --- a/dev/CliTestReporter.php +++ b/dev/CliTestReporter.php @@ -114,12 +114,10 @@ class CliTestReporter extends SapphireTestReporter { protected function writeTest($test) { if ($test['status'] != TEST_SUCCESS) { $filteredTrace = array(); - $ignoredClasses = array('TestRunner'); foreach($test['trace'] as $item) { if(isset($item['file']) && strpos($item['file'], 'PHPUnit/Framework') === false - && (!isset($item['class']) || !in_array($item['class'], $ignoredClasses))) { - + && !isset($item['class'])) { $filteredTrace[] = $item; } diff --git a/dev/SapphireTest.php b/dev/SapphireTest.php index 4751294f2..da8850868 100644 --- a/dev/SapphireTest.php +++ b/dev/SapphireTest.php @@ -1,6 +1,4 @@ pushManifest($classManifest, false); + SapphireTest::set_test_class_manifest($classManifest); + + SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest( + BASE_PATH, project(), true, $flush + )); + + Config::inst()->pushConfigStaticManifest(new SS_ConfigStaticManifest( + BASE_PATH, true, $flush + )); + + // Invalidate classname spec since the test manifest will now pull out new subclasses for each internal class + // (e.g. Member will now have various subclasses of DataObjects that implement TestOnly) + DataObject::reset(); + } + /** * Returns true if we are currently using a temporary database */ diff --git a/dev/SapphireTestSuite.php b/dev/SapphireTestSuite.php deleted file mode 100644 index 0d109c06e..000000000 --- a/dev/SapphireTestSuite.php +++ /dev/null @@ -1,23 +0,0 @@ -groups as $group) { - if($group[0] instanceof SapphireTest) $group[0]->setUpOnce(); - } - } - - public function tearDown() { - foreach($this->groups as $group) { - if($group[0] instanceof SapphireTest) $group[0]->tearDownOnce(); - } - } -} diff --git a/dev/TestRunner.php b/dev/TestRunner.php deleted file mode 100755 index 641036600..000000000 --- a/dev/TestRunner.php +++ /dev/null @@ -1,444 +0,0 @@ -URL Options - * - SkipTests: A comma-separated list of test classes to skip (useful when running dev/tests/all) - * - * See {@link browse()} output for generic usage instructions. - * - * @package framework - * @subpackage testing - */ -class TestRunner extends Controller { - - /** @ignore */ - private static $default_reporter; - - private static $url_handlers = array( - '' => 'browse', - 'coverage/module/$ModuleName' => 'coverageModule', - 'coverage/suite/$SuiteName!' => 'coverageSuite', - 'coverage/$TestCase!' => 'coverageOnly', - 'coverage' => 'coverageAll', - 'cleanupdb' => 'cleanupdb', - 'module/$ModuleName' => 'module', - 'suite/$SuiteName!' => 'suite', - 'all' => 'all', - 'build' => 'build', - '$TestCase' => 'only' - ); - - private static $allowed_actions = array( - 'index', - 'browse', - 'coverage', - 'coverageAll', - 'coverageModule', - 'coverageSuite', - 'coverageOnly', - 'cleanupdb', - 'module', - 'suite', - 'all', - 'build', - 'only' - ); - - /** - * @var Array Blacklist certain directories for the coverage report. - * Filepaths are relative to the webroot, without leading slash. - * - * @see http://www.phpunit.de/manual/current/en/appendixes.configuration.html - * #appendixes.configuration.blacklist-whitelist - */ - static $coverage_filter_dirs = array( - '*/thirdparty', - '*/tests', - '*/lang', - ); - - /** - * Override the default reporter with a custom configured subclass. - * - * @param string $reporter - */ - public static function set_reporter($reporter) { - if (is_string($reporter)) $reporter = new $reporter; - self::$default_reporter = $reporter; - } - - /** - * Pushes a class and template manifest instance that include tests onto the - * top of the loader stacks. - */ - public static function use_test_manifest() { - $flush = false; - if(isset($_GET['flush']) && ($_GET['flush'] === '1' || $_GET['flush'] == 'all')) { - $flush = true; - } - - $classManifest = new SS_ClassManifest( - BASE_PATH, true, $flush - ); - - SS_ClassLoader::instance()->pushManifest($classManifest, false); - SapphireTest::set_test_class_manifest($classManifest); - - SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest( - BASE_PATH, project(), true, $flush - )); - - Config::inst()->pushConfigStaticManifest(new SS_ConfigStaticManifest( - BASE_PATH, true, $flush - )); - - // Invalidate classname spec since the test manifest will now pull out new subclasses for each internal class - // (e.g. Member will now have various subclasses of DataObjects that implement TestOnly) - DataObject::reset(); - } - - public function init() { - parent::init(); - - $canAccess = (Director::isDev() || Director::is_cli() || Permission::check("ADMIN")); - if(!$canAccess) return Security::permissionFailure($this); - - if (!self::$default_reporter) self::set_reporter(Director::is_cli() ? 'CliDebugView' : 'DebugView'); - - if(!PhpUnitWrapper::has_php_unit()) { - die("Please install PHPUnit using Composer"); - } - } - - public function Link() { - return Controller::join_links(Director::absoluteBaseURL(), 'dev/tests/'); - } - - /** - * Run test classes that should be run with every commit. - * Currently excludes PhpSyntaxTest - */ - public function all($request, $coverage = false) { - self::use_test_manifest(); - $tests = ClassInfo::subclassesFor('SapphireTest'); - array_shift($tests); - unset($tests['FunctionalTest']); - - // Remove tests that don't need to be executed every time - unset($tests['PhpSyntaxTest']); - - foreach($tests as $class => $v) { - $reflection = new ReflectionClass($class); - if(!$reflection->isInstantiable()) unset($tests[$class]); - } - - $this->runTests($tests, $coverage); - } - - /** - * Run test classes that should be run before build - i.e., everything possible. - */ - public function build() { - self::use_test_manifest(); - $tests = ClassInfo::subclassesFor('SapphireTest'); - array_shift($tests); - unset($tests['FunctionalTest']); - foreach($tests as $class => $v) { - $reflection = new ReflectionClass($class); - if(!$reflection->isInstantiable()) unset($tests[$class]); - } - - $this->runTests($tests); - } - - /** - * Browse all enabled test cases in the environment - */ - public function browse() { - self::use_test_manifest(); - self::$default_reporter->writeHeader(); - self::$default_reporter->writeInfo('Available Tests', false); - if(Director::is_cli()) { - $tests = ClassInfo::subclassesFor('SapphireTest'); - $relativeLink = Director::makeRelative($this->Link()); - echo "sake {$relativeLink}all: Run all " . count($tests) . " tests\n"; - echo "sake {$relativeLink}coverage: Runs all tests and make test coverage report\n"; - echo "sake {$relativeLink}module/: Runs all tests in a module folder\n"; - foreach ($tests as $test) { - echo "sake {$relativeLink}$test: Run $test\n"; - } - } else { - echo '
'; - $tests = ClassInfo::subclassesFor('SapphireTest'); - asort($tests); - echo "

Link() . "all\">Run all " . count($tests) . " tests

"; - echo "

Link() . "coverage\">Runs all tests and make test coverage report

"; - echo "
"; - foreach ($tests as $test) { - echo "

Link() . "$test\">Run $test

"; - } - echo '
'; - } - - self::$default_reporter->writeFooter(); - } - - /** - * Run a coverage test across all modules - */ - public function coverageAll($request) { - self::use_test_manifest(); - $this->all($request, true); - } - - /** - * Run only a single coverage test class or a comma-separated list of tests - */ - public function coverageOnly($request) { - $this->only($request, true); - } - - /** - * Run coverage tests for one or more "modules". - * A module is generally a toplevel folder, e.g. "mysite" or "framework". - */ - public function coverageModule($request) { - $this->module($request, true); - } - - public function cleanupdb() { - SapphireTest::delete_all_temp_dbs(); - } - - /** - * Run only a single test class or a comma-separated list of tests - */ - public function only($request, $coverage = false) { - self::use_test_manifest(); - if($request->param('TestCase') == 'all') { - $this->all(); - } else { - $classNames = explode(',', $request->param('TestCase')); - foreach($classNames as $className) { - if(!class_exists($className) || !is_subclass_of($className, 'SapphireTest')) { - user_error("TestRunner::only(): Invalid TestCase '$className', cannot find matching class", - E_USER_ERROR); - } - } - - $this->runTests($classNames, $coverage); - } - } - - /** - * Run tests for one or more "modules". - * A module is generally a toplevel folder, e.g. "mysite" or "framework". - */ - public function module($request, $coverage = false) { - self::use_test_manifest(); - $classNames = array(); - $moduleNames = explode(',', $request->param('ModuleName')); - - $ignored = array('functionaltest', 'phpsyntaxtest'); - - foreach($moduleNames as $moduleName) { - $classNames = array_merge( - $classNames, - $this->getTestsInDirectory($moduleName, $ignored) - ); - } - - $this->runTests($classNames, $coverage); - } - - /** - * Find all test classes in a directory and return an array of them. - * @param string $directory To search in - * @param array $ignore Ignore these test classes if they are found. - * @return array - */ - protected function getTestsInDirectory($directory, $ignore = array()) { - $classes = ClassInfo::classes_for_folder($directory); - return $this->filterTestClasses($classes, $ignore); - } - - /** - * Find all test classes in a file and return an array of them. - * @param string $file To search in - * @param array $ignore Ignore these test classes if they are found. - * @return array - */ - protected function getTestsInFile($file, $ignore = array()) { - $classes = ClassInfo::classes_for_file($file); - return $this->filterTestClasses($classes, $ignore); - } - - /** - * @param array $classes to search in - * @param array $ignore Ignore these test classes if they are found. - */ - protected function filterTestClasses($classes, $ignore) { - $testClasses = array(); - if($classes) { - foreach($classes as $className) { - if( - class_exists($className) && - is_subclass_of($className, 'SapphireTest') && - !in_array($className, $ignore) - ) { - $testClasses[] = $className; - } - } - } - return $testClasses; - } - - /** - * Run tests for a test suite defined in phpunit.xml - */ - public function suite($request, $coverage = false) { - self::use_test_manifest(); - $suite = $request->param('SuiteName'); - $xmlFile = BASE_PATH.'/phpunit.xml'; - if(!is_readable($xmlFile)) { - user_error("TestRunner::suite(): $xmlFile is not readable", E_USER_ERROR); - } - $xml = simplexml_load_file($xmlFile); - $suite = $xml->xpath("//phpunit/testsuite[@name='$suite']"); - if(empty($suite)) { - user_error("TestRunner::suite(): couldn't find the $suite testsuite in phpunit.xml"); - } - $suite = array_shift($suite); - $classNames = array(); - if(isset($suite->directory)) { - foreach($suite->directory as $directory) { - $classNames = array_merge($classNames, $this->getTestsInDirectory($directory)); - } - } - if(isset($suite->file)) { - foreach($suite->file as $file) { - $classNames = array_merge($classNames, $this->getTestsInFile($file)); - } - } - - $this->runTests($classNames, $coverage); - } - - /** - * Give us some sweet code coverage reports for a particular suite. - */ - public function coverageSuite($request) { - return $this->suite($request, true); - } - - /** - * @param array $classList - * @param boolean $coverage - */ - public function runTests($classList, $coverage = false) { - $startTime = microtime(true); - - // disable xdebug, as it messes up test execution - if(function_exists('xdebug_disable')) xdebug_disable(); - - ini_set('max_execution_time', 0); - - $this->setUp(); - - // Optionally skip certain tests - $skipTests = array(); - if($this->getRequest()->getVar('SkipTests')) { - $skipTests = explode(',', $this->getRequest()->getVar('SkipTests')); - } - - $abstractClasses = array(); - foreach($classList as $className) { - // Ensure that the autoloader pulls in the test class, as PHPUnit won't know how to do this. - class_exists($className); - $reflection = new ReflectionClass($className); - if ($reflection->isAbstract()) { - array_push($abstractClasses, $className); - } - } - - $classList = array_diff($classList, $skipTests, $abstractClasses); - - // run tests before outputting anything to the client - $suite = new PHPUnit_Framework_TestSuite(); - natcasesort($classList); - foreach($classList as $className) { - // Ensure that the autoloader pulls in the test class, as PHPUnit won't know how to do this. - class_exists($className); - $suite->addTest(new SapphireTestSuite($className)); - } - - // Remove the error handler so that PHPUnit can add its own - restore_error_handler(); - - self::$default_reporter->writeHeader("SilverStripe Test Runner"); - if (count($classList) > 1) { - self::$default_reporter->writeInfo("All Tests", "Running test cases: ",implode(", ", $classList)); - } elseif (count($classList) == 1) { - self::$default_reporter->writeInfo(reset($classList), ''); - } else { - // border case: no tests are available. - self::$default_reporter->writeInfo('', ''); - } - - // perform unit tests (use PhpUnitWrapper or derived versions) - $phpunitwrapper = PhpUnitWrapper::inst(); - $phpunitwrapper->setSuite($suite); - $phpunitwrapper->setCoverageStatus($coverage); - - // Make sure TearDown is called (even in the case of a fatal error) - $self = $this; - register_shutdown_function(function() use ($self) { - $self->tearDown(); - }); - - $phpunitwrapper->runTests(); - - // get results of the PhpUnitWrapper class - $reporter = $phpunitwrapper->getReporter(); - $results = $phpunitwrapper->getFrameworkTestResults(); - - if(!Director::is_cli()) echo '
'; - $reporter->writeResults(); - - $endTime = microtime(true); - if(Director::is_cli()) echo "\n\nTotal time: " . round($endTime-$startTime,3) . " seconds\n"; - else echo "

Total time: " . round($endTime-$startTime,3) . " seconds

\n"; - - if(!Director::is_cli()) echo '
'; - - // Put the error handlers back - $errorHandler = Injector::inst()->get('ErrorHandler'); - $errorHandler->start(); - - if(!Director::is_cli()) self::$default_reporter->writeFooter(); - - $this->tearDown(); - - // Todo: we should figure out how to pass this data back through Director more cleanly - if(Director::is_cli() && ($results->failureCount() + $results->errorCount()) > 0) exit(2); - } - - public function setUp() { - // The first DB test will sort out the DB, we don't have to - SSViewer::flush_template_cache(); - } - - public function tearDown() { - SapphireTest::kill_temp_db(); - } -} diff --git a/dev/phpunit/PhpUnitWrapper.php b/dev/phpunit/PhpUnitWrapper.php deleted file mode 100644 index 014504ba6..000000000 --- a/dev/phpunit/PhpUnitWrapper.php +++ /dev/null @@ -1,272 +0,0 @@ -coverage; - } - - /** - * Setter for $coverage (@see $coverage). - * @parameter $value Boolean - */ - public function setCoverageStatus($value) { - $this->coverage = $value; - } - - /** - * Getter for $suite (@see $suite). - * @return PHPUnit_Framework_TestSuite - */ - public function getSuite() { - return $this->suite; - } - - /** - * Setter for $suite (@see $suite). - * @param $value PHPUnit_Framework_TestSuite - */ - public function setSuite($value) { - $this->suite = $value; - } - - /** - * Getter for $reporter (@see $reporter). - * @return PHPUnit_Framework_TestListener - */ - public function getReporter() { - return $this->reporter; - } - - /** - * Setter for $reporter (@see $reporter). - * @param $value PHPUnit_Framework_TestListener - */ - public function setReporter($value) { - $this->reporter = $value; - } - - /** - * Getter for $results (@see $results). - * @return PHPUnit_Framework_TestResult - */ - public function getFrameworkTestResults() { - return $this->results; - } - - /** - * Setter for $results (@see $results). - * @param $value PHPUnit_Framework_TestResult - */ - public function setFrameworkTestResults($value) { - $this->results = $value; - } - - /** - * Getter for $version (@see $version). - * @return String - */ - public function getVersion() { - return $this->version; - } - - /** - * Loads and initiates phpunit, based on the available phpunit version. - * - * @return PhpUnitWrapper Instance of the php-wrapper class - */ - public static function inst() { - - if (self::$phpunit_wrapper == null) { - // Loaded via autoloader, composer or other generic - if (class_exists('PHPUnit_Runner_Version')) { - self::$phpunit_wrapper = new PhpUnitWrapper_Generic(); - } - // 3.5 detection - else if (fileExistsInIncludePath("/PHPUnit/Autoload.php")) { - self::$phpunit_wrapper = new PhpUnitWrapper_3_5(); - } - // 3.4 detection - else if (fileExistsInIncludePath("/PHPUnit/Framework.php")) { - self::$phpunit_wrapper = new PhpUnitWrapper_3_4(); - } - // No version found - will lead to an error - else { - self::$phpunit_wrapper = new PhpUnitWrapper(); - } - self::$phpunit_wrapper->init(); - - } - return self::$phpunit_wrapper; - } - - /** - * Returns true if one of the two supported PHPUNIT versions is installed. - * - * @return boolean true if PHPUnit has been installed on the environment. - */ - public static function has_php_unit() { - return (Bool) self::inst()->getVersion(); - } - - /** - * Implements method, defined in the interface IPhpUnitWrapper:init (@see IPhpUnitWrapper). - * This wrapper class doesn't require any initialisation. - */ - public function init() { - } - - /** - * This method is called before the unittests are performed. - * This wrapper implements the non-PHPUnit version which means that unit tests - * can not be performed. - * @throws PhpUnitWrapper_Excption - */ - protected function beforeRunTests() { - throw new PhpUnitWrapper_Exception('Method \'beforeRunTests\' not implemented in PhpUnitWrapper.'); - } - - /** - * This method is called after the unittests are performed. - * This wrapper implements the non-PHPUnit version which means that unit tests - * can not be performed. - * @throws PhpUnitWrapper_Excption - */ - protected function afterRunTests() { - throw new PhpUnitWrapper_Exception('Method \'afterRunTests\' not implemented in PhpUnitWrapper.'); - } - - /** - * Perform all tests, added to the suite and initialises SilverStripe to collect - * the results of the unit tests. - * - * This method calls @see beforeRunTests and @see afterRunTests. - */ - public function runTests() { - - if(Director::is_cli()) { - $this->setReporter( new CliTestReporter() ); - } else { - $this->setReporter( new SapphireTestReporter() ); - } - - if ($this->getFrameworkTestResults() == null) { - $this->setFrameworkTestResults(new PHPUnit_Framework_TestResult()); - } - $this->getFrameworkTestResults()->addListener( $this->getReporter() ); - - $this->beforeRunTests(); - $this->getSuite()->run($this->getFrameworkTestResults()); - $this->afterRunTests(); - } - - /** - * Returns an array containing all the module folders in the base dir. - * - * @return array - */ - protected function moduleDirectories() { - $files = scandir(BASE_PATH); - $modules = array(); - foreach($files as $file) { - if(is_dir(BASE_PATH . "/$file") && file_exists(BASE_PATH . "/$file/_config.php")) { - $modules[] = $file; - } - } - return $modules; - } -} - -/** - * Interface, implementing the general PHPUnit wrapper API. - */ -interface IPhpUnitWrapper { - - public function init(); - - public function runTests(); -} - - -/** - * PHPUnitWrapper Exception class - */ -class PhpUnitWrapper_Exception extends Exception {} - - -// If PHPUnit is not installed on the local environment, declare the class to -// ensure that missing class declarations are available to avoind any PHP fatal -// errors. -// -if(!PhpUnitWrapper::has_php_unit()) { - /** - * PHPUnit is a testing framework that can be installed using Composer. - * It's not bundled with SilverStripe, you will need to install it yourself. - * - * @package framework - * @subpackage testing - */ - class PHPUnit_Framework_TestCase { - - } -} diff --git a/dev/phpunit/PhpUnitWrapper_3_4.php b/dev/phpunit/PhpUnitWrapper_3_4.php deleted file mode 100644 index e2a839c51..000000000 --- a/dev/phpunit/PhpUnitWrapper_3_4.php +++ /dev/null @@ -1,76 +0,0 @@ -getCoverageStatus()) { - // blacklist selected folders from coverage report - $modules = $this->moduleDirectories(); - - foreach(TestRunner::config()->coverage_filter_dirs as $dir) { - if($dir[0] == '*') { - $dir = substr($dir, 1); - foreach ($modules as $module) { - PHPUnit_Util_Filter::addDirectoryToFilter(BASE_PATH . '/' . $dir); - } - } else { - PHPUnit_Util_Filter::addDirectoryToFilter(BASE_PATH . '/' . $dir); - } - } - $this->getFrameworkTestResults()->collectCodeCoverageInformation(true); - } - } - - /** - * Overwrites afterRunTests. Creates coverage report and clover report - * if required. - */ - protected function afterRunTests() { - - if($this->getCoverageStatus()) { - - if(!file_exists(ASSETS_PATH . '/coverage-report')) { - mkdir(ASSETS_PATH . '/coverage-report'); - } - - $ret = PHPUnit_Util_Report::render($this->getFrameworkTestResults(), ASSETS_PATH . '/coverage-report/'); - - $coverageApp = ASSETS_PATH . '/coverage-report/' - . preg_replace('/[^A-Za-z0-9]/','_',preg_replace('/(\/$)|(^\/)/','',Director::baseFolder())) . '.html'; - $coverageTemplates = ASSETS_PATH . '/coverage-report/' - . preg_replace('/[^A-Za-z0-9]/','_',preg_replace('/(\/$)|(^\/)/','',realpath(TEMP_FOLDER))) . '.html'; - - echo "

Coverage reports available here:

"; - } - } - -} diff --git a/dev/phpunit/PhpUnitWrapper_3_5.php b/dev/phpunit/PhpUnitWrapper_3_5.php deleted file mode 100644 index 456a21dcc..000000000 --- a/dev/phpunit/PhpUnitWrapper_3_5.php +++ /dev/null @@ -1,25 +0,0 @@ -getCoverageStatus()) { - $this->coverage = new PHP_CodeCoverage(); - $coverage = $this->coverage; - - $filter = $coverage->filter(); - $modules = $this->moduleDirectories(); - - foreach(TestRunner::config()->coverage_filter_dirs as $dir) { - if($dir[0] == '*') { - $dir = substr($dir, 1); - foreach ($modules as $module) { - $filter->addDirectoryToBlacklist(BASE_PATH . "/$module/$dir"); - } - } else { - $filter->addDirectoryToBlacklist(BASE_PATH . '/' . $dir); - } - } - - $filter->addFileToBlacklist(__FILE__, 'PHPUNIT'); - - $coverage->start(self::get_test_name()); - } - } - - /** - * Overwrites afterRunTests. Creates coverage report and clover report - * if required. - */ - protected function afterRunTests() { - - if($this->getCoverageStatus()) { - $coverage = $this->coverage; - $coverage->stop(); - - $writer = new PHP_CodeCoverage_Report_HTML(); - $writer->process($coverage, ASSETS_PATH.'/code-coverage-report'); - } - } - -} diff --git a/docs/en/02_Developer_Guides/06_Testing/00_Unit_Testing.md b/docs/en/02_Developer_Guides/06_Testing/00_Unit_Testing.md index 0821980a0..3d3c3c618 100644 --- a/docs/en/02_Developer_Guides/06_Testing/00_Unit_Testing.md +++ b/docs/en/02_Developer_Guides/06_Testing/00_Unit_Testing.md @@ -51,112 +51,6 @@ the [PHPUnit](http://www.phpunit.de) documentation. It provides a lot of fundame documentation. -## Running Tests - -### PHPUnit Binary - -The `phpunit` binary should be used from the root directory of your website. - - :::bash - phpunit - # Runs all tests - - phpunit framework/tests/ - # Run all tests of a specific module - - phpunit framework/tests/filesystem - # Run specific tests within a specific module - - phpunit framework/tests/filesystem/FolderTest.php - # Run a specific test - - phpunit framework/tests '' flush=all - # Run tests with optional `$_GET` parameters (you need an empty second argument) - -
-The manifest is not flushed when running tests. Add `flush=all` to the test command to do this (see above example.) -
- -
-If phpunit is not installed globally on your machine, you may need to replace the above usage of `phpunit` with the full -path (e.g `vendor/bin/phpunit framework/tests`) -
- -
-All command-line arguments are documented on [phpunit.de](http://www.phpunit.de/manual/current/en/textui.html). -
- -### Via a Web Browser - -Executing tests from the command line is recommended, since it most closely reflects test runs in any automated testing -environments. If for some reason you don't have access to the command line, you can also run tests through the browser. - - http://yoursite.com/dev/tests - - -### Via the CLI - -The [sake](../cli) executable that comes with SilverStripe can trigger a customized `[api:TestRunner]` class that -handles the PHPUnit configuration and output formatting. While the custom test runner a handy tool, it's also more -limited than using `phpunit` directly, particularly around formatting test output. - - :::bash - sake dev/tests/all - # Run all tests - - sake dev/tests/module/framework,cms - # Run all tests of a specific module (comma-separated) - - sake dev/tests/FolderTest,OtherTest - # Run specific tests (comma-separated) - - sake dev/tests/all "flush=all&foo=bar" - # Run tests with optional `$_GET` parameters - - sake dev/tests/all SkipTests=MySkippedTest - # Skip some tests - -## Making Tests Run Fast -A major impedement to testing is that by default tests are extremely slow to run. There are two things that can be done to speed them up: - -### Disable xDebug -Unless executing a coverage report there is no need to have xDebug enabled. - - :::bash - # Disable xdebug - sudo php5dismod xdebug - - # Run tests - phpunit framework/tests/ - - # Enable xdebug - sudo php5enmod xdebug - -### Use SQLite In Memory -SQLIte can be configured to fun in memory as opposed to disk and this makes testing an order of magnitude faster. To effect this change add the following to mysite/_config.php - this enables an optional flag to switch between MySQL and SQLite. Note also that the package silverstripe/sqlite3 will need installed, version will vary depending on which version of SilverStripe is being tested. - - :::php - if(Director::isDev()) { - if(isset($_GET['db']) && ($db = $_GET['db'])) { - global $databaseConfig; - if($db == 'sqlite3') { - $databaseConfig['type'] = 'SQLite3Database'; - $databaseConfig['path'] = ':memory:'; - } - } - } - -To use SQLite append '' db=sqlite3 after the phpunit command. - - :::bash - phpunit framework/tests '' db=sqlite3 - -### Speed Comparison -Testing against a medium sized module with 93 tests: -* SQLite - 16.15s -* MySQL - 314s -This means using SQLite will run tests over 20 times faster. - ## Test Databases and Fixtures SilverStripe tests create their own database when the test starts. New `ss_tmp` databases are created using the same @@ -170,7 +64,7 @@ permissions to create new databases on your server.
The test database is rebuilt every time one of the test methods is run. Over time, you may have several hundred test -databases on your machine. To get rid of them is a call to `http://yoursite.com/dev/tests/cleanupdb` +databases on your machine. To get rid of them, run `sake dev/tasks/CleanupTestDatabasesTask`.
## Custom PHPUnit Configuration @@ -200,11 +94,6 @@ needs. -
-This configuration file doesn't apply for running tests through the "sake" wrapper -
- - ### setUp() and tearDown() In addition to loading data through a [Fixture File](fixtures), a test case may require some additional setup work to be @@ -286,38 +175,6 @@ It's important to remember that the `parent::setUp();` functions will need to be Config::inst()->get('ClassName', 'var_name'); // this will be 'var_value' } -## Generating a Coverage Report - -PHPUnit can generate a code coverage report ([docs](http://www.phpunit.de/manual/current/en/code-coverage-analysis.html)) -by executing the following commands. - - :::bash - phpunit --coverage-html assets/coverage-report - # Generate coverage report for the whole project - - phpunit --coverage-html assets/coverage-report mysite/tests/ - # Generate coverage report for the "mysite" module - -
-These commands will output a report to the `assets/coverage-report/` folder. To view the report, open the `index.html` -file within a web browser. -
- -Typically, only your own custom PHP code in your project should be regarded when producing these reports. To exclude -some `thirdparty/` directories add the following to the `phpunit.xml` configuration file. - - :::xml - - - framework/dev/ - framework/thirdparty/ - cms/thirdparty/ - - - mysite/thirdparty/ - - - ## Related Documentation * [How to Write a SapphireTest](how_tos/write_a_sapphiretest) @@ -326,6 +183,5 @@ some `thirdparty/` directories add the following to the `phpunit.xml` configurat ## API Documentation -* [api:TestRunner] * [api:SapphireTest] * [api:FunctionalTest] diff --git a/docs/en/02_Developer_Guides/06_Testing/index.md b/docs/en/02_Developer_Guides/06_Testing/index.md index f37aae6de..584e96660 100644 --- a/docs/en/02_Developer_Guides/06_Testing/index.md +++ b/docs/en/02_Developer_Guides/06_Testing/index.md @@ -16,30 +16,60 @@ the [Testing Glossary](testing_glossary). To get started now, follow the install If you are familiar with PHP coding but new to unit testing then check out Mark's presentation [Getting to Grips with SilverStripe Testing](http://www.slideshare.net/maetl/getting-to-grips-with-silverstripe-testing). -You should also read over [the PHPUnit manual](http://www.phpunit.de/manual/current/en/). It provides a lot of +You should also read over the [PHPUnit manual](http://www.phpunit.de/manual/current/en/). It provides a lot of fundamental concepts that we build on in this documentation. -Unit tests are not included in the zip/tar.gz SilverStripe [downloads](http://www.silverstripe.org/software/download/) so to get them, install SilverStripe [with composer](/getting_started/composer). +## Running Tests -## Invoking phpunit +In order to run tests, you need to install SilverStripe using [/getting-started/composer](Composer), +which will pull in the required development dependencies to run tests. +These are not included in the standard archive downloads provided from silverstripe.org. -Once you have used composer to create your project, `cd` to your project root. Composer will have installed PHPUnit alongside the required PHP classes into the `vendor/bin/` directory. +Tests are run from the commandline, in your webroot folder: -If you don't want to invoke PHPUnit through its full path (`vendor/bin/phpunit`), add `./vendor/bin` to your $PATH, or symlink phpunit into the root directory of your website: + * `vendor/bin/phpunit`: Runs all tests (as defined by `phpunit.xml`) + * `vendor/bin/phpunit framework/tests/`: Run all tests of a specific module + * `vendor/bin/phpunit framework/tests/filesystem`: Run specific tests within a specific module + * `vendor/bin/phpunit framework/tests/filesystem/FolderTest.php`: Run a specific test + * `vendor/bin/phpunit framework/tests '' flush=all`: Run tests with optional request parameters (note the empty second argument) -- `PATH=./vendor/bin:$PATH` in your shell's profile script; **or** -- `ln -s vendor/bin/phpunit phpunit` at the command prompt in your project root +Check the PHPUnit manual for all available [command line arguments](http://www.phpunit.de/manual/current/en/textui.html). + +On Linux or OSX, you can avoid typing the full path on every invocation by adding `vendor/bin` +to your `$PATH` definition in the shell profile (usually `~/.profile`): `PATH=./vendor/bin:$PATH` + +## Generating a Coverage Report + +PHPUnit can generate a code coverage report ([docs](http://www.phpunit.de/manual/current/en/code-coverage-analysis.html)) +which shows you how much of your logic is executed by your tests. This is very useful to determine gaps in tests. + + :::bash + vendor/bin/phpunit --coverage-html + +To view the report, open the `index.html` in `` in a web browser. + +Typically, only your own custom PHP code in your project should be regarded when producing these reports. To exclude +some `thirdparty/` directories add the following to the `phpunit.xml` configuration file. + + :::xml + + + framework/dev/ + framework/thirdparty/ + cms/thirdparty/ + + + mysite/thirdparty/ + + ## Configuration -### phpunit.xml - -The `phpunit` executable can be configured by command line arguments or through an XML file. File-based configuration has +The `phpunit` executable can be configured by [command line arguments](http://www.phpunit.de/manual/current/en/textui.html) +or through an XML file. File-based configuration has the advantage of enforcing certain rules across test executions (e.g. excluding files from code coverage reports), and of course this information can be version controlled and shared with other team members. -**Note: This doesn't apply for running tests through the "sake" wrapper** - SilverStripe comes with a default `phpunit.xml.dist` that you can use as a starting point. Copy the file into a new `phpunit.xml` and customize to your needs - PHPUnit will auto-detect its existence, and prioritize it over the default file. @@ -50,7 +80,7 @@ There's nothing stopping you from creating multiple XML files (see the `--config ### Database Permissions -SilverStripe tests create thier own database when they are run. Because of this the database user in your config file +SilverStripe tests create their own temporary database on every execution. Because of this the database user in your config file should have the appropriate permissions to create new databases on your server, otherwise tests will not run. ## Writing Tests @@ -71,57 +101,4 @@ Tutorials and recipes for creating tests using the SilverStripe framework: * [Creating a SilverStripe test](how_tos/write_a_sapphiretest): Writing tests to check core data objects * [Creating a functional test](how_tos/write_a_functionaltest): An overview of functional tests and how to write a functional test -* [Testing Outgoing Email](how_tos/testing_email): An overview of the built-in email testing code - -## Running Tests - -### Via the "phpunit" Binary on Command Line - -The `phpunit` binary should be used from the root directory of your website. - - # Runs all tests defined in phpunit.xml - phpunit - - # Run all tests of a specific module - phpunit framework/tests/ - - # Run specific tests within a specific module - phpunit framework/tests/filesystem - - # Run a specific test - phpunit framework/tests/filesystem/FolderTest.php - - # Run tests with optional `$_GET` parameters (you need an empty second argument) - phpunit framework/tests '' flush=all - -All command-line arguments are documented on -[phpunit.de](http://www.phpunit.de/manual/current/en/textui.html). - -### Via the "sake" Wrapper on Command Line - -The [sake](/developer_guides/cli/) executable that comes with SilverStripe can trigger a customized -`[api:TestRunner]` class that handles the PHPUnit configuration and output formatting. -While the custom test runner a handy tool, its also more limited than using `phpunit` directly, -particularly around formatting test output. - - # Run all tests - sake dev/tests/all - - # Run all tests of a specific module (comma-separated) - sake dev/tests/module/framework,cms - - # Run specific tests (comma-separated) - sake dev/tests/FolderTest,OtherTest - - # Run tests with optional `$_GET` parameters - sake dev/tests/all flush=all - - # Skip some tests - sake dev/tests/all SkipTests=MySkippedTest - -### Via Web Browser - -Executing tests from the command line is recommended, since it most closely reflects -test runs in any automated testing environments. However, you can also run tests through the browser: - - http://localhost/dev/tests +* [Testing Outgoing Email](how_tos/testing_email): An overview of the built-in email testing code \ No newline at end of file diff --git a/docs/en/02_Developer_Guides/17_CLI/index.md b/docs/en/02_Developer_Guides/17_CLI/index.md index 56d0709c8..f1b02fc31 100644 --- a/docs/en/02_Developer_Guides/17_CLI/index.md +++ b/docs/en/02_Developer_Guides/17_CLI/index.md @@ -80,11 +80,6 @@ Sake is particularly useful for running build tasks. :::bash sake dev/build "flush=1" -Or running unit tests.. - - :::bash - sake dev/tests/all - It can also be handy if you have a long running script.. :::bash diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md index 8eeb99a66..100d03920 100644 --- a/docs/en/04_Changelogs/4.0.0.md +++ b/docs/en/04_Changelogs/4.0.0.md @@ -28,6 +28,7 @@ * `File` is now versioned, and should be published before they can be used on the frontend. See section on [Migrating File DataObject from 3.x to 4.0](#migrating-file-dataobject-from-3x-to-40) below for upgrade notes. + * Removed `dev/tests/` controller in favour of standard `vendor/bin/phpunit` command ## New API @@ -47,6 +48,11 @@ * `Configurable` Provides Config API helper methods * `Injectable` Provides Injector API helper methods * `Extensible` Allows extensions to be applied + * Removed ability to run tests via web requests (`http://mydomain.com/dev/tests`), use the standard CLI command instead (`vendor/bin/phpunit`) + * Removed `dev/jstests/` controller (no replacement) + * Moved test database cleanup task from `sake dev/tests/cleanupdb` to `sake dev/tasks/CleanupTestDatabasesTask` + * Removed `TestRunner` and `JSTestRunner` APIs + * Removed `PhpUnitWrapper`, `PhpUnitWrapper_3_4`, `PhpUnitWrapper_3_5`, `PhpUnitWrapper_Generic`, `SapphireTestSuite` APIs * `HtmlEditorConfig` is now an abstract class, with a default implementation `TinyMCEConfig` for the built in TinyMCE editor. * `HtmlEditorField::setEditorConfig` may now take an instance of a `HtmlEditorConfig` class, as well as a diff --git a/tasks/CleanupTestDatabasesTask.php b/tasks/CleanupTestDatabasesTask.php new file mode 100644 index 000000000..38b46492b --- /dev/null +++ b/tasks/CleanupTestDatabasesTask.php @@ -0,0 +1,26 @@ + - - - Test of Banana javascript - - - - - - - - - - - - diff --git a/tests/javascript/bananas/bananas.js b/tests/javascript/bananas/bananas.js deleted file mode 100644 index 55f5e2b7d..000000000 --- a/tests/javascript/bananas/bananas.js +++ /dev/null @@ -1,16 +0,0 @@ -Banana = { - getAmount: function() { - return 2; - }, - getColor: function() { - return 'brown'; - } -} -describe("The banana", function() { - it("should have two left", function() { - expect(Banana.getAmount()).toEqual(2); - }); - it("should be yellow", function() { - expect(Banana.getColor()).toEqual('yellow'); - }); -});