mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Removed custom dev/tests/ execution
Rely on standard vendor/bin/phpunit via CLI instead. See https://github.com/silverstripe/silverstripe-framework/issues/4254 - Not disabling xdebug. That should be harmful, and is actively harming test development - No longer able to specifically skip tests via the `SkipTests` request parameter. Use phpunit.xml groups and the `--exclude-group` CLI argument instead - No longer able to specify multiple comma-separated module folders. use phpunit.xml groups and the `--group` CLI argument instead - Not explicitly calling `SSViewer::flush_template_cache()` (was never the case on running `phpunit` CLI anyway, and shouldn't be required any more)
This commit is contained in:
parent
a16588aac3
commit
d1af214ef5
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
<?php
|
||||
require_once 'TestRunner.php';
|
||||
|
||||
/**
|
||||
* Test case class for the Sapphire framework.
|
||||
* Sapphire unit testing is based on PHPUnit, but provides a number of hooks into our data model that make it easier
|
||||
@ -124,7 +122,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
|
||||
|
||||
|
||||
/**
|
||||
* Determines if unit tests are currently run (via {@link TestRunner}).
|
||||
* Determines if unit tests are currently run, flag set during test bootstrap.
|
||||
* This is used as a cheap replacement for fully mockable state
|
||||
* in certain contiditions (e.g. access checks).
|
||||
* Caution: When set to FALSE, certain controllers might bypass
|
||||
@ -823,6 +821,36 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
|
||||
return var_export($extracted, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a class and template manifest instance that include tests onto the
|
||||
* top of the loader stacks.
|
||||
*/
|
||||
public static function use_test_manifest() {
|
||||
$flush = true;
|
||||
if(isset($_GET['flush']) && $_GET['flush'] === '0') {
|
||||
$flush = false;
|
||||
}
|
||||
|
||||
$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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we are currently using a temporary database
|
||||
*/
|
||||
|
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Light wrapper around {@link PHPUnit_Framework_TestSuite}
|
||||
* which allows to have {@link setUp()} and {@link tearDown()}
|
||||
* methods which are called just once per suite, not once per
|
||||
* test method in each suite/case.
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage testing
|
||||
*/
|
||||
class SapphireTestSuite extends PHPUnit_Framework_TestSuite {
|
||||
public function setUp() {
|
||||
foreach($this->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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,444 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage testing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller that executes PHPUnit tests.
|
||||
*
|
||||
* Alternatively, you can also use the "phpunit" binary directly by
|
||||
* pointing it to a file or folder containing unit tests.
|
||||
* See phpunit.dist.xml in the webroot for configuration details.
|
||||
*
|
||||
* <h2>URL Options</h2>
|
||||
* - 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/<modulename>: Runs all tests in a module folder\n";
|
||||
foreach ($tests as $test) {
|
||||
echo "sake {$relativeLink}$test: Run $test\n";
|
||||
}
|
||||
} else {
|
||||
echo '<div class="trace">';
|
||||
$tests = ClassInfo::subclassesFor('SapphireTest');
|
||||
asort($tests);
|
||||
echo "<h3><a href=\"" . $this->Link() . "all\">Run all " . count($tests) . " tests</a></h3>";
|
||||
echo "<h3><a href=\"" . $this->Link() . "coverage\">Runs all tests and make test coverage report</a></h3>";
|
||||
echo "<hr />";
|
||||
foreach ($tests as $test) {
|
||||
echo "<h3><a href=\"" . $this->Link() . "$test\">Run $test</a></h3>";
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
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 '<div class="trace">';
|
||||
$reporter->writeResults();
|
||||
|
||||
$endTime = microtime(true);
|
||||
if(Director::is_cli()) echo "\n\nTotal time: " . round($endTime-$startTime,3) . " seconds\n";
|
||||
else echo "<p class=\"total-time\">Total time: " . round($endTime-$startTime,3) . " seconds</p>\n";
|
||||
|
||||
if(!Director::is_cli()) echo '</div>';
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
@ -1,272 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage dev
|
||||
*/
|
||||
|
||||
/**
|
||||
* This method checks if a given filename exists in the include path (defined
|
||||
* in php.ini.
|
||||
*
|
||||
* @return boolean when the file has been found in the include path.
|
||||
*/
|
||||
function fileExistsInIncludePath($filename) {
|
||||
$paths = explode(PATH_SEPARATOR, ini_get('include_path'));
|
||||
foreach($paths as $path) {
|
||||
if(substr($path,-1) == DIRECTORY_SEPARATOR) $path = substr($path,0,-1);
|
||||
if(@file_exists($path."/".$filename)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* PHPUnit Wrapper class.
|
||||
* Base class for PHPUnit wrapper classes to support different PHPUnit versions.
|
||||
* The current implementation supports PHPUnit 3.4 and PHPUnit 3.5.
|
||||
*/
|
||||
class PhpUnitWrapper implements IPhpUnitWrapper {
|
||||
|
||||
/**
|
||||
* Flag if coverage report shall be generated or not.
|
||||
* @var boolean
|
||||
*/
|
||||
private $coverage = false;
|
||||
|
||||
/**
|
||||
* PHPUnit-TestSuite class. The tests, added to this suite are performed
|
||||
* in this test-run.
|
||||
* @var PHPUnit_Framework_TestSuite
|
||||
*/
|
||||
private $suite = null;
|
||||
|
||||
/**
|
||||
* @var PHPUnit_Framework_TestResult
|
||||
*/
|
||||
private $results = null;
|
||||
|
||||
/**
|
||||
* @var PHPUnit_Framework_TestListener
|
||||
*/
|
||||
private $reporter = null;
|
||||
|
||||
/**
|
||||
* Shows the version, implemented by the phpunit-wrapper class instance.
|
||||
* This instance implements no phpunit, the version is null.
|
||||
* @var String
|
||||
*/
|
||||
protected $version = null;
|
||||
|
||||
private static $phpunit_wrapper = null;
|
||||
|
||||
/**
|
||||
* Getter for $coverage (@see $coverage).
|
||||
* @return boolean
|
||||
*/
|
||||
public function getCoverageStatus() {
|
||||
return $this->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 {
|
||||
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage dev
|
||||
*/
|
||||
|
||||
/**
|
||||
* PHPUnit Wrapper class. Implements the correct behaviour for PHPUnit V3.4.
|
||||
*/
|
||||
class PhpUnitWrapper_3_4 extends PhpUnitWrapper {
|
||||
|
||||
public function getVersion() {
|
||||
return 'PhpUnit V3.4';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the wrapper class.
|
||||
*/
|
||||
public function init() {
|
||||
parent::init();
|
||||
require_once 'PHPUnit/Framework.php';
|
||||
require_once 'PHPUnit/Util/Report.php';
|
||||
require_once 'PHPUnit/TextUI/TestRunner.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites beforeRunTests. Initiates coverage-report generation if
|
||||
* $coverage has been set to true (@see setCoverageStatus).
|
||||
*/
|
||||
protected function beforeRunTests() {
|
||||
|
||||
if($this->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 "<p>Coverage reports available here:<ul>
|
||||
<li><a href=\"$coverageApp\">Coverage report of the application</a></li>
|
||||
<li><a href=\"$coverageTemplates\">Coverage report of the templates</a></li>
|
||||
</ul>";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage dev
|
||||
*/
|
||||
|
||||
class PhpUnitWrapper_3_5 extends PhpUnitWrapper_Generic {
|
||||
|
||||
public function getVersion() {
|
||||
return 'PhpUnit V3.5';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the wrapper class.
|
||||
*/
|
||||
public function init() {
|
||||
if(!class_exists('PHPUnit_Framework_TestCase')) {
|
||||
require_once 'PHP/CodeCoverage.php';
|
||||
require_once 'PHP/CodeCoverage/Report/HTML.php';
|
||||
require_once 'PHPUnit/Autoload.php';
|
||||
require_once 'PHP/CodeCoverage/Filter.php';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Generic PhpUnitWrapper.
|
||||
* Originally intended for use with Composer based installations, but will work
|
||||
* with any fully functional autoloader.
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage dev
|
||||
*/
|
||||
class PhpUnitWrapper_Generic extends PhpUnitWrapper {
|
||||
|
||||
/**
|
||||
* Returns a version string, like 3.7.34 or 4.2-dev.
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion() {
|
||||
return PHPUnit_Runner_Version::id();
|
||||
}
|
||||
|
||||
protected $coverage = null;
|
||||
|
||||
protected static $test_name = 'SapphireTest';
|
||||
|
||||
public static function get_test_name() {
|
||||
return static::$test_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites beforeRunTests. Initiates coverage-report generation if
|
||||
* $coverage has been set to true (@see setCoverageStatus).
|
||||
*/
|
||||
protected function beforeRunTests() {
|
||||
|
||||
if($this->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');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -51,112 +51,6 @@ the [PHPUnit](http://www.phpunit.de) documentation. It provides a lot of fundame
|
||||
documentation.
|
||||
</div>
|
||||
|
||||
## 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)
|
||||
|
||||
<div class="alert" markdown="1">
|
||||
The manifest is not flushed when running tests. Add `flush=all` to the test command to do this (see above example.)
|
||||
</div>
|
||||
|
||||
<div class="alert" markdown="1">
|
||||
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`)
|
||||
</div>
|
||||
|
||||
<div class="info" markdown="1">
|
||||
All command-line arguments are documented on [phpunit.de](http://www.phpunit.de/manual/current/en/textui.html).
|
||||
</div>
|
||||
|
||||
### 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.
|
||||
|
||||
<div class="notice" markdown="1">
|
||||
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`.
|
||||
</div>
|
||||
|
||||
## Custom PHPUnit Configuration
|
||||
@ -200,11 +94,6 @@ needs.
|
||||
</groups>
|
||||
</phpunit>
|
||||
|
||||
<div class="alert" markdown="1">
|
||||
This configuration file doesn't apply for running tests through the "sake" wrapper
|
||||
</div>
|
||||
|
||||
|
||||
### 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
|
||||
|
||||
<div class="notice" markdown="1">
|
||||
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.
|
||||
</div>
|
||||
|
||||
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
|
||||
<filter>
|
||||
<blacklist>
|
||||
<directory suffix=".php">framework/dev/</directory>
|
||||
<directory suffix=".php">framework/thirdparty/</directory>
|
||||
<directory suffix=".php">cms/thirdparty/</directory>
|
||||
|
||||
<!-- Add your custom rules here -->
|
||||
<directory suffix=".php">mysite/thirdparty/</directory>
|
||||
</blacklist>
|
||||
</filter>
|
||||
|
||||
## 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]
|
||||
|
@ -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 <output-folder> <optional-tests-folder>
|
||||
|
||||
To view the report, open the `index.html` in `<output-folder>` 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
|
||||
<filter>
|
||||
<blacklist>
|
||||
<directory suffix=".php">framework/dev/</directory>
|
||||
<directory suffix=".php">framework/thirdparty/</directory>
|
||||
<directory suffix=".php">cms/thirdparty/</directory>
|
||||
|
||||
<!-- Add your custom rules here -->
|
||||
<directory suffix=".php">mysite/thirdparty/</directory>
|
||||
</blacklist>
|
||||
</filter>
|
||||
|
||||
## 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
|
@ -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
|
||||
|
@ -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
|
||||
|
26
tasks/CleanupTestDatabasesTask.php
Normal file
26
tasks/CleanupTestDatabasesTask.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* Cleans up leftover databases from aborted test executions (starting with ss_tmpdb)
|
||||
* Task is restricted to users with administrator rights or running through CLI.
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage tasks
|
||||
*/
|
||||
class CleanuPTestDatabasesTask extends BuildTask {
|
||||
protected $title = 'Deletes all temporary test databases';
|
||||
|
||||
protected $description = 'Cleans up leftover databases from aborted test executions (starting with ss_tmpdb)';
|
||||
|
||||
public function init() {
|
||||
parent::init();
|
||||
|
||||
if(!Permission::check('ADMIN') && !Director::is_cli()) {
|
||||
return Security::permissionFailure($this);
|
||||
}
|
||||
}
|
||||
|
||||
public function run() {
|
||||
SapphireTest::delete_all_temp_dbs();
|
||||
}
|
||||
|
||||
}
|
@ -65,7 +65,7 @@ class EncryptAllPasswordsTask extends BuildTask {
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo This should really be taken care of by TestRunner
|
||||
* @todo This should really be taken care of by the testing framework
|
||||
*/
|
||||
protected function debugMessage($msg) {
|
||||
if(class_exists('SapphireTest', false) && !SapphireTest::is_running_test()) {
|
||||
|
@ -7,12 +7,6 @@
|
||||
// Make sure display_errors is on
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
// Check we're using at least PHPUnit 3.5
|
||||
if(version_compare(PHPUnit_Runner_Version::id(), '3.5', '<')) {
|
||||
echo 'PHPUnit 3.5 required to run tests using bootstrap.php';
|
||||
die();
|
||||
}
|
||||
|
||||
// Fake the script name and base
|
||||
global $_SERVER;
|
||||
if (!$_SERVER) $_SERVER = array();
|
||||
@ -50,7 +44,7 @@ global $databaseConfig;
|
||||
DB::connect($databaseConfig);
|
||||
|
||||
// Now set a fake REQUEST_URI
|
||||
$_SERVER['REQUEST_URI'] = BASE_URL . '/dev';
|
||||
$_SERVER['REQUEST_URI'] = BASE_URL;
|
||||
|
||||
// Fake a session
|
||||
$_SESSION = null;
|
||||
@ -58,8 +52,7 @@ $_SESSION = null;
|
||||
// Prepare manifest autoloader
|
||||
$controller = new FakeController();
|
||||
|
||||
// Get test manifest
|
||||
TestRunner::use_test_manifest();
|
||||
SapphireTest::use_test_manifest();
|
||||
|
||||
SapphireTest::set_is_running_test(true);
|
||||
|
||||
|
@ -1,44 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test of Banana javascript</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<!-- This is the minimal jasmin libraries needed -->
|
||||
<link rel="stylesheet" type="text/css" href="../../../thirdparty/jasmine/lib/jasmine.css">
|
||||
<script type="application/javascript" src="../../../thirdparty/jasmine/lib/jasmine.js"></script>
|
||||
<script type="application/javascript" src="../../../thirdparty/jasmine/lib/jasmine-html.js"></script>
|
||||
|
||||
<script type="application/javascript" >
|
||||
/**
|
||||
* This is the javascript under test
|
||||
*/
|
||||
Banana = {
|
||||
getAmount: function() {
|
||||
return 2;
|
||||
},
|
||||
getColor: function() {
|
||||
return 'brown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the testcase and should be moved out to a individual
|
||||
* javascript so other testdrivers can run them.
|
||||
*/
|
||||
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');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="application/javascript">
|
||||
jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
|
||||
jasmine.getEnv().execute();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -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');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user