![]() |
|
sfPhpunitPlugin - 1.0.10Integrate phpunit framework with symfony one |
|
![]() |
38
users
Sign-in
to change your status |
Integrate phpunit framework with symfony one |
The main idea of the plugin is to integrate phpunit and symfony more closer. Make the process of test writing more standard, flexible and easy for symfony developers. Another idea is to allow developers use features that only they want and not to inflict any extra requirments.
There are some main features.
* Flexible way to store and run tests.
* Manage fixtures, separate them to different folders.
* Manage symfony contexts.
* Configure phpunit in a symfony configuration way.
* Create testing infrastructure in one touch.
* Provide custom TestCases, for example, SeleniumTestCase, AmfTestCase.
| Name | Status | |
|---|---|---|
|
|
lead | ten.rku <<ta>> raltokm |
Copyright (c) 2008,2009 Pablo Godel, Frank Stelzer
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
| Version | License | API | Released |
|---|---|---|---|
| 1.0.20stable | MIT license | 1.2.0stable | 13/05/2011 |
| 1.0.14stable | MIT license | 1.2.0stable | 15/09/2010 |
| 1.0.13stable | MIT license | 1.2.0stable | 09/08/2010 |
| 1.0.11stable | MIT license | 1.2.0stable | 16/06/2010 |
| 1.0.10stable | MIT license | 1.2.0stable | 04/06/2010 |
| 1.0.9stable | MIT license | 1.2.0stable | 28/05/2010 |
| Version | License | API | Released |
|---|---|---|---|
| 1.0.20stable | MIT license | 1.2.0stable | 13/05/2011 |
| 1.0.14stable | MIT license | 1.2.0stable | 15/09/2010 |
| 1.0.13stable | MIT license | 1.2.0stable | 09/08/2010 |
| 1.0.11stable | MIT license | 1.2.0stable | 16/06/2010 |
| 1.0.10stable | MIT license | 1.2.0stable | 04/06/2010 |
| 1.0.9stable | MIT license | 1.2.0stable | 28/05/2010 |
| Version | License | API | Released |
|---|---|---|---|
| 1.0.20stable | MIT license | 1.2.0stable | 13/05/2011 |
| 1.0.14stable | MIT license | 1.2.0stable | 15/09/2010 |
| 1.0.13stable | MIT license | 1.2.0stable | 09/08/2010 |
| 1.0.11stable | MIT license | 1.2.0stable | 16/06/2010 |
| 1.0.10stable | MIT license | 1.2.0stable | 04/06/2010 |
| 1.0.9stable | MIT license | 1.2.0stable | 28/05/2010 |
| 1.0.8stable | MIT license | 1.2.0stable | 20/01/2010 |
| 1.0.7stable | MIT license | 1.2.0stable | 30/12/2009 |
| 1.0.4stable | MIT license | 1.2.0stable | 19/01/2009 |
| 1.0.3stable | MIT license | 1.1.0stable | 10/11/2008 |
| 1.0.2stable | MIT license | 1.1.0stable | 10/11/2008 |
| 1.0.1stable | MIT license | 1.1.0stable | 07/11/2008 |
| Version | License | API | Released |
|---|---|---|---|
| 1.0.4stable | MIT license | 1.2.0stable | 19/01/2009 |
| 1.0.3stable | MIT license | 1.1.0stable | 10/11/2008 |
| 1.0.2stable | MIT license | 1.1.0stable | 10/11/2008 |
| 1.0.1stable | MIT license | 1.1.0stable | 07/11/2008 |
| 0.0.1beta | MIT license | 0.0.1stable | 06/11/2008 |
| Version | License | API | Released |
|---|---|---|---|
| 0.2.2devel | MIT license | 0.2.0devel | 06/08/2010 |
It's a plugin to join Symfony framework with a PHPUnit testing one.
1.download:
pear package
php symfony plugin:install sfPhpunitPlugin
svn tag
svn checkout http://svn.symfony-project.com/plugins/sfPhpunitPlugin/tags/sfPhpunitPlugin-1.0.9
dev version:
svn checkout http://svn.symfony-project.com/plugins/sfPhpunitPlugin/branches/1.2-4
2.enable it in ProjectConfiguration:
class ProjectConfiguration extends sfProjectConfiguration
{
public function setup()
{
$this->enablePlugins('sfPhpunitPlugin');
}
}
The main idea of the plugin is to integrate phpunit and symfony more closer. Make the process of test writing more standard, flexible and easy for symfony developers. Another idea is to allow developers use features that only they want and not to inflict any extra requirments.
There are some main features.
The plugin needs some directories and files. It can create them for itself automatically or you can create them manually, using command:
php symfony phpunit:init
This is the result of the command execution:
>> phpunit Created dir /home/maksim/tmp/sf_sandbox/test/phpunit
>> phpunit Created dir /home/maksim/tmp/sf_sandbox/test/phpunit/units
>> phpunit Created dir /home/maksim/tmp/sf_sandbox/test/phpunit/functionals
>> phpunit Created dir /home/maksim/tmp/sf_sandbox/test/phpunit/models
>> phpunit Created dir /home/maksim/tmp/sf_sandbox/test/phpunit/fixtures
>> phpunit Created dir /home/maksim/tmp/sf_sandbox/test/phpunit/fixtures/units
>> phpunit Created dir /home/maksim/tmp/sf_sandbox/test/phpunit/fixtures/functionals
>> phpunit Created dir /home/maksim/tmp/sf_sandbox/test/phpunit/fixtures/models
>> phpunit Generate file /home/maksim/tmp/sf_sandbox/test/phpunit/BasePhpunitTestSuite.php
>> phpunit Generate file /home/maksim/tmp/sf_sandbox/test/phpunit/AllTests.php
As you can see some folders and files were created:
You can run tests in several ways. Each of them has its pluses and minuses.
The most flexible way is to run tests using a symfony task:
All project tests:
php symfony phpunit:runtest
All project tests and plugins ones.
php symfony phpunit:runtest --and-plugins
All tests in folder (the absolute path is sf_root_dir/test/phpunit/foo):
php symfony phpunit:runtest foo/
All tests in folder and subfolders (the absolute path is sf_root_dir/test/phpunit/foo):
php symfony phpunit:runtest foo/*
A concret test:
php symfony phpunit:runtest foo/FooTestCase.php
The absolute path is also aceptable:
php symfony phpunit:runtest /path_to_the_project/test/phpunit/foo/*
And relative path from the project root as well:
php symfony phpunit:runtest 'test/phpunit/foo/*'
But in this case you cannot use any of the phpunit command options (like coverage or report generation). To solve this problem we have auto generated file test/phpunit/AllTest.php. It can be used with a phpunit command to run all project tests. Example of calling all tests with extra phpunit options from the project root dir:
phpunit
--log-junit=/path_to_log/phpunit.xml
--coverage-clover=/path_to_log/phpunit.coverage.xml
--coverage-html=/path_to_log/coverage
test/phpunit/AllTest.php
If you are using Eclipse PDT there is a way how to run a single test or all tests in a folder (just in a folder but not in sub folders) just from the Eclipse. All you want to do is to add "Extra Tool" called phpunit for example and with options as in the picture:

That's it. Now you can select a test file and run it from Eclipse.
To setup or teardown something, you should use _start and _end methods instead of phpunit standart's setUp and tearDown. If you overwrite setUp or tearDown methods you can miss/break some plugin functionality. The same is right for test case classes. Let's look at the BasePhpunitTestSuite.php in test/phpunit folder. Pay attention to _start and _end methods. You have to use them.
<?php
class BasePhpunitTestSuite extends sfBasePhpunitTestSuite
implements sfPhpunitContextInitilizerInterface
{
/**
* Dev hook for custom "setUp" stuff
*/
protected function _start()
{
// do your initialization here
}
/**
* Dev hook for custom "tearDown" stuff
*/
protected function _end()
{
// clean up something here
}
}
There is a test loader for collecting tests and building a tree. If you choose a test and try to execute it the loader will do next job:
If there is not any of your suites all tests will be added to the root suite BasePhpunitTestSuite. It was done this way to solve the problem of the same environment whether you run a single test or all tests, or just a sub folder.
You can simply manage symfony's contexts. What you need to do is: implement sfPhpunitContextInitilizerInterface to a TestSuite and implement required methods. For example the auto generated BasePhpunitTestSuite implement this interface for the first application, was found in your project. So all running tests will have a context with application 'frontend' and environment 'test'. You can change it, modifying the file.
<?php
class BasePhpunitTestSuite extends sfBasePhpunitTestSuite
implements sfPhpunitContextInitilizerInterface
{
public function getApplication()
{
return 'frontend';
}
/**
*
* This function is also required by the interface but implemented in the parent class: sfBasePhpunitTestSuite.
*You can always overwride it on your own.
*/
//public function getEnvironment()
//{
// return sfConfig::get('sf_environment', 'test');
//}
}
If You don't need a context at all, you can remove the interface and methods from the testsuite. If You want to init context just for a sub folder (for example models), create a testsuite with the interface implemented there, and all your model tests will have a sfContext initialized.
Very important thing in testing process is fixture managing. It takes half time of all the test writing to create good environment. So a good mechanism to work with them can save a lot of time. Also it helps to keep tests simple and easy to understand.
There are four places where you can save your fixtures. You can use all of them or just one.
First of all you have to choose which fixtures to use. There are two types currently available:
It's a simple example how to define a fixture type you want to use. As you can see it is very easy to tell what you want to use. You have to implement one of the following interfaces to a testcase class.
<?php
class FooTestCase extends PHPUnit_Framework_TestCase
implements sfPhpunitFixtureDoctrineAggregator
// for propel
//implements sfPhpunitFixturePropelAggregator
{
public function getPackageFixtureDir()
{
// implement this method
}
public function getOwnFixtureDir()
{
// implement this method
}
public function getCommonFixtureDir()
{
// implement this method
}
public function getSymfonyFixtureDir()
{
// implement this method
}
public function testFoo()
{
// create fixture object by hands:
$fixture = sfPhpunitFixture::build($this, $options = array());
//now you can use fixture instance to work with fixtures.
$path = $fixture->getFileOwn('fixture-file.txt');
// test code here
}
}
After you finish this and run this test case you will see something like this:
>> phpunit Created dir /home/maksim/projec...s/FooTestCase
>> phpunit Created dir /home/maksim/projec...s/FooTestCase
PHPUnit 3.4.12 by Sebastian Bergmann.
.
Time: 7 seconds, Memory: 37.00Mb
OK (1 test, 8 assertions)
It is phpunit:init task which helps you with directories for fixture storing. It creates package and own directory if they do not exist.
If you do not want to define any methods manually you can extend from sfBasePhpunitTestCase (where all those methods are already implemented) and implement interface. sfBasePhpunitTestCase also contains fixture method which returns instance of sfPhpunitFixture, so you do not have to create it manually.
<?php
class FooTestCase extends sfBasePhpunitTestCase
implements sfPhpunitFixtureDoctrineAggregator
// for propel
//implements sfPhpunitFixturePropelAggregator
{
public function testFoo()
{
$path = $this->fixture()->getFileOwn('fixture-file.txt');
// test code here
}
}
In this case you get standard plugin fixture directories. All fixture directories are subdirectories of sf_root_dir/test/phpunit/fixtures dir. You can always overwrite any of those methods to change a directory target.
Let's say we have FooTestCase.php file in sf_root_dir/test/phpunit/units directory. Standard directories for the testcase will be:
This example shows you how to get path to file stored in fixture directories.
<?php
class FooTestCase extends sfBasePhpunitTestCase
implements sfPhpunitFixtureDoctrineAggregator
// for propel
//implements sfPhpunitFixturePropelAggregator
{
public function testFoo()
{
$path = $this->fixture()->getFileOwn('foo.zip');
// result: sf_root_dir/test/phpunit/fixtures/units/FooTestCase/foo.zip
$path = $this->fixture()->getFilePackage('foo.zip');
// result: sf_root_dir/test/phpunit/fixtures/units/foo.zip
$path = $this->fixture()->getFileCommon('foo.zip');
// result: sf_root_dir/test/phpunit/fixtures/common/foo.zip
$path = $this->fixture()->getFileSymfony('foo.zip');
// result: sf_root_dir/data/fixture/foo.zip
}
}
If the file does not exist, an exception will be thrown.
<?php
class FooTestCase extends sfBasePhpunitTestCase
implements sfPhpunitFixtureDoctrineAggregator
// for propel
//implements sfPhpunitFixturePropelAggregator
{
public function testFoo()
{
$path = $this->fixture()->getFileOwn('not-exist-file.zip');
// result: exception is thrown.
}
}
The same idea works for directories as well:
<?php
class FooTestCase extends sfBasePhpunitTestCase
implements sfPhpunitFixtureDoctrineAggregator
// for propel
//implements sfPhpunitFixturePropelAggregator
{
public function testFoo()
{
$path = $this->fixture()->getDirOwn();
// result: sf_root_dir/test/phpunit/fixtures/units/FooTestCase
}
}
You can even load fixtures to the database. The database fixture file is standard propel\doctrine one. For example I'll show how to load users.doctrine.yml to the database.
<?php
class FooTestCase extends sfBasePhpunitTestCase
implements sfPhpunitFixtureDoctrineAggregator
// for propel
//implements sfPhpunitFixturePropelAggregator
{
public function testFoo()
{
$path = $this->fixture()->loadOwn('users');
// load next file: sf_root_dir/test/phpunit/fixtures/units/FooTestCase/users.doctrine.yml
$path = $this->fixture()->loadPackage('users');
// load next file: sf_root_dir/test/phpunit/fixtures/units/users.doctrine.yml
$path = $this->fixture()->loadCommon('users');
// load next file: sf_root_dir/test/phpunit/fixtures/common/users.doctrine.yml
$path = $this->fixture()->loadSymfony('users');
// load next file: sf_root_dir/data/fixture/users.doctrine.yml
}
}
As you see you have to give only file name without extension.
The same for propel, but extension will be *.propel.yml.
If the file does not exist, an exception will be thrown.
<?php
class FooTestCase extends sfBasePhpunitTestCase
implements sfPhpunitFixtureDoctrineAggregator
// for propel
//implements sfPhpunitFixturePropelAggregator
{
public function testFoo()
{
$this->fixture()->loadOwn('not-exist-fixtures');
// result: exception is thrown.
}
}
For loadOwn, loadPackage, loadCommon, loadSymfony methods plugin supports fluent interface So you can do something like this:
<?php
class FooTestCase extends sfBasePhpunitTestCase
implements sfPhpunitFixtureDoctrineAggregator
// for propel
//implements sfPhpunitFixturePropelAggregator
{
public function testFoo()
{
$this->fixture()
->loadOwn('users')
->loadCommon('posts')
->loadPackage('comments');
}
}
To clean the whole database and load new fixtures:
<?php
class FooTestCase extends sfBasePhpunitTestCase
implements sfPhpunitFixtureDoctrineAggregator
// for propel
//implements sfPhpunitFixturePropelAggregator
{
public function testFoo()
{
$this->fixture()
->clean()
// remove all data from the test database
->loadOwn('users');
// only users will be in the database
}
}
It is very important to keep speed of the testing as fast as possible. Yml fixture loading is an expensive operation and can slow down your testing. To avoid this you can use snapshot feature.
And second thing where snapshots can be useful, group fixtures under a single name, load them in one command, using this name.
For example you can create some snapshots in root test suite and use them in any test case later.
<?php
class BasePhpunitTestSuite extends sfBasePhpunitTestSuite
implements sfPhpunitFixturePropelAggregator
{
/**
* Dev hook for custom "setUp" stuff
*/
protected function _start()
{
$this->fixture()
->clean()
// clean up tables used for storing snapshot data.
->cleanSnapshots()
->loadCommon('users')
->loadCommon('categories')
->loadCommon('customers')
->doSnapshot('common');
}
}
Then load the snapshot in FooTestCase, for example.
<?php
class FooTestCase extends sfBasePhpunitTestCase
implements sfPhpunitFixtureDoctrineAggregator
// for propel
//implements sfPhpunitFixturePropelAggregator
{
public function testFoo()
{
$this->fixture()->clean()->loadSnapshot('common');
//and some additional stuff if needed.
$this->fixture()->loadCommon('comments');
// load fixtures form file sf_root_dir/data/fixture/comments.doctrine.yml
// test code here.
}
}
To clean all snapshots use method
$this->fixture()->cleanSnapshots();
It is better to use snapshots because they are kept in the test database and so work very fast.
Fixture object can be init with some options such as file extension and database connection (by default it is taken from the sfContext).
In future it can be done through the phpunit.yml but right now you have to rewrite _initFixture method:
class FooTestCase extends sfBasePhpunitTestCase
implements sfPhpunitFixtureDoctrineAggregator
// for propel
//implements sfPhpunitFixturePropelAggregator
{
protected function _initFixture(array $options = array())
{
$options = array(
'connection' => 'your connection',
'fixture_ext' => 'your database fixture extension. just *.yml for example',
'snapshot-table-prefix' => 'the table prefix used for snapshots',
);
return parent::_initFixture($options);
}
}
This feature allows you to get Doctrine\Propel data object by its name in fixture file. For examlpe we have users.doctrine.yml file in the common fixture directory:
sfGuardUser:
TestUser:
username: user
password: test
RegistredUser2:
username: Lorne
password: Green
sgu_admin:
username: admin
password: test
is_super_admin: true
And how it works in test case:
class FooTestCase extends sfBasePhpunitTestCase
implements sfPhpunitFixtureDoctrineAggregator
// for propel
//implements sfPhpunitFixturePropelAggregator
{
public function testFoo()
{
// loads users from sf_root_dir/test/phpunit/fixtures/common/users.doctrine.yml
$this->fixture()->clean()->loadCommon('users);
// using method method get
$testUser = $this->fixture()->get('sfGuardUser_TestUser');
$this->assertType('sfGuardUser', $testUser);
//shorter version
$registeredUser = $this->fixture('sfGuardUser_RegistredUser2');
$this->assertType('sfGuardUser', $registeredUser);
}
}
This functionality works perfectly with Propel. For Doctrine there are some bugs known.
This test cases are not necessary to be used but can be very helpful.
The sfBasePhpunitSeleniumTestCase class is extend of PHPUnit_Extensions_SeleniumTestCase standart phpunit class. It allows you to set options of a selenium server in phpunit.yml file and they will be handled automaticaly by sfBasePhpunitSeleniumTestCase class.
The selenium section of phpunit.yml config:
all:
selenium:
remote_project_dir:
#you can used it in case of the test application and selenium server are run on different computers.
#but you have to upload the file stored in fixtures through web page.
#You need to mount prj dir to selenium computer and define this option.
#From the test case use for example method fixture()->getDirOwnAsRemote()
coverage:
collect: false
coverageScript: phpunit_coverage.php
driver:
name: false
browser: '*firefox'
browser_url: false
host: false
port: false
timeout: false
http_timeout: false
sleep: false
wait: false
Create your own phpunit.yml file in sf_root_dir/config, for example, and define your own options there. The phpunit.yml file works in the same way as all other symfony configs (cascade, merging, project config dir, app config dir).
The config can look like:
all:
selenium:
driver:
browser: '*chrome'
browser_url: 'http://google.com'
timeout: 10
http_timeout: 10
host: selenium-server.com
And test:
class GoogleTestCase extends sfBasePhpunitSeleniumTestCase
{
protected function _start()
{
//$this->setAutoStop(false);
$this->start();
$this->open('/');
$this->windowMaximize();
}
public function testGoogle()
{
// your google test here
}
}
The plugin also contains sfBasePhpunitAmfTestCase class for testing your AMF services. It depends on SabreAMF library. So you have to be sure you installed it before.
It is a kind of functional test because it emulates Flex client and sends data through the network using http protocol.
First you need to set an Amf endpoint in phpunit.yml. It is highly recommended to make test in the same project (run test and send request on this server).
all:
amf:
endpoint: 'http://your-server.com/amfendpoint'
class Amf_Service_ClientTestCase extends sfBasePhpunitAmfTestCase
implements sfPhpunitFixturePropelAggregator
{
public function testNewClient()
{
$service = 'Service_Test.testNewClient';
$params = array('test');
$response = $this->_getClient()->sendRequest($serviceName, $params);
// test response from the service
}
The example do the same as the one above:
class Amf_Service_ClientTestCase extends sfBasePhpunitAmfTestCase
implements sfPhpunitFixturePropelAggregator
{
// define a service name
protected $_amfservice = 'Service_Test';
public function testNewClient()
{
// testNewClient is handled by __call method.
// It call Service_Test.testNewClient with one param 'test'
$response = $this->service()->testNewClient('test');
// test response from the service
}
}
If you use AMF data mapping, this method can be very helpful for you. Rewrite it, keeping in mind the order of array parameters:
class Amf_Service_ClientTestCase extends sfBasePhpunitAmfTestCase
implements sfPhpunitFixturePropelAggregator
{
public function testNewClient()
{
// your amf test
}
protected function _getMappedClasses()
{
// array('flex_class' => 'php_class');
return array('ClientFlex' => 'ClientPHP');
}
}
The sfBasePhpunitAmfTestCase class contains helpful method getStub. It can be used only for making stub objects, not mocks. The method has the same interface as getMock method.
This method allows you to create stub objects more simply then getMock method:
Compare these two examples:
<?php
class FooTestCase extends sfBasePhpunitTestCase
{
public function testFoo()
{
$stub = $this->getMock('DataManager', array('getFoo', 'getBar'));
$stub->expects($this->any())->method('getFoo')->will($this->returnValue('foo'));
$stub->expects($this->any())->method('getBar')->will($this->returnValue('bar'));
// test code here
}
}
and:
<?php
class FooTestCase extends sfBasePhpunitTestCase
{
public function testFoo()
{
$stub = $this->getStub('DataManager', array(
'getFoo' => 'foo',
'getBar' => 'bar'));
// test code here
}
}
The getStub method used $this->any() method to set numbers of the stubbed methods called. You can use more strict version of this method:
$this->getStubStrict( ... );
This method used $this->atLeastOnce(),
If you want to create two stubs with cross reference to each other, the next way can help you:
<?php
class FooTestCase extends sfBasePhpunitTestCase
{
public function testFoo()
{
$stubFoo = $this->getStub('Foo', array('getBar' => $this->stubLater()));
$stubBar = $this->getStub('Bar', array('getFoo' => $stubFoo));
$stubFoo->expects($this->any())->method('getBar' => $stubBar);
// test code here
}
}
It not only is very important to test a project code but plugins code as well. The plugin can run phpunit tests written for a symfony plugin. There are some restrictions:
To run a plugin tests with a project ones use a command:
php symfony phpunit:runtest --and-plugins
test:
doctrine:
param:
dsn: mysql:host=127.0.0.1;dbname=project_test
#dsn: sqlite:////file_to/test.db?mode=0666
#dsn: "sqlite::memory:"
all:
doctrine:
class: sfDoctrineDatabase
param:
dsn: mysql:host=127.0.0.1;dbname=project
username: mysqluser
password: mysqlpass
Any feedbacks with ideas, bugs, misunderstandings, troubles are very welcome.
I want to say a big thank the whole FormaPro company. The people from there use the plugin from very early versions and gives me very valuable feedbacks.
