26
Best practice of PHPUnit testing a symfony 1.4 plugin
24 Comments · Posted by Christian in The right tool
I started this blog to occupy myself with topics like unit testing, continuous integration and code quality in general. As a subject for my studies I quickly released a symfony plugin (sfImageTransformExtraPlugin) that I already worked on for a while and decided to excercise all my learnings on it. So you can peek at the sources to see more details.
As all the early posts of this blog were kept in German I am going to summarise the best practice of PHPUnit testing a symfony 1.4 plugin in this post.
Of course I have to say that the term best practice refers to my current state of knowledge.
Why PHPUnit?
Actually when I started this blog I was determined to use symfonys own lime unit test implementation because it is integrated and documented within the symfony documentation. But the more I worked with it the more I was put off by it.
lime is ok for being run during development by the developer itself but it can not easily be used in continuous integration as it is not fully xUnit compliant and does not provide all infos you want to see there. lime is also very procedual and the more complex your application become the more spaghetti code you will have to maintain. And foremost lime is only used in the symfony world and learning it will not have any benefit for you in other projects.
PHPUnit on the other hand is the de facto standard for PHP. It is object oriented as well as well documented and maintained and it is fully xUnit compliant thus can easily be integrated into your continuous integration workflow.
Prerequisites / Assumptions about your plugin
In the following I assume that you generated your plugin by running the symfony task generate:plugin (provided by sfTaskExtraPlugin) and that you did not change the files and directories inside your plugins /test directory.
This is important as there are many files that were generated for you that may look curious to you but will make a lot more sense once you learned how to use them.
Of course these files were created with lime in mind and in the following I will show you what you need to change to use the same structure with PHPUnit.
Bootstrapping
In your plugins /test/bootstrap you will find a script for bootstrapping for your unit tests unit.php.
// /plugins/myTestPlugin/test/bootstrap/unit.php
getSymfonyLibDir().'/vendor/lime/lime.php';
function myTestPlugin_autoload_again($class)
{
$autoload = sfSimpleAutoload::getInstance();
$autoload->reload();
return $autoload->autoload($class);
}
spl_autoload_register('myTestPlugin_autoload_again');
if (file_exists($config = dirname(__FILE__).'/../../config/myTestPluginConfiguration.class.php'))
{
require_once $config;
$plugin_configuration = new myTestPluginConfiguration($configuration, dirname(__FILE__).'/../..', 'myTestPlugin');
}
else
{
$plugin_configuration = new sfPluginConfigurationGeneric($configuration, dirname(__FILE__).'/../..', 'myTestPlugin');
}
There are two things to notice here:
- The use of
$_SERVER['SYMFONY']
When executing unit tests you have to tell the bootstrap where to look for the symfony core libraries. For lime this is actually done by the symfony test tasks but for PHPUnit we don’t have a task available so we need to run the following command first (only once per console session).
$ export SYMFONY="/absolute/path/to/lib/vendor/symfony/lib"
I will explain the reason behind this in a moment.
- The project configuration is looaded from a fixture project
If you have a look at your plugins /test/fixtures/project directory you will recognise a complete symfony project just like you run generate:project.
This is going to be your test environment.
The reason for both of these two features is this:
The whole point of a symfony plugin is to be reusable and to be reusable it needs to be as project independant as possible.
Therefor it will not be unit testet in a project where the project developers can easily create situations where your tests will fail but in a fixture project you as a plugin developer can completely control.And because this fixture project is included in your project we don’t want to also include the whole symfony code.
But a symfony plugin will always have at least one dependancy and that is towards symfony itself, so we have to make the location of it known to our plugin before running the tests.
Now for PHPUnit we need to replace the following lines from the code above.
$configuration = new sfProjectConfiguration(dirname(__FILE__).'/../fixtures/project');
require_once $configuration->getSymfonyLibDir().'/vendor/lime/lime.php';
With these.
$projectPath = dirname(__FILE__).'/../fixtures/project';
/** configuration of the fixture project */
require_once($projectPath.'/config/ProjectConfiguration.class.php');
$configuration = new ProjectConfiguration($projectPath);
The purpose of this bootstrap file is to create the environment our plugin expects. So if all of your plugins functionalities need an instance of sfContext you would create it here or if they would all depend on the database you would create a connection here as well.
But remember there should be as few dependencies as possible!
How to write your test cases?
This is the easies part of all. Just refer to the PHPUnit documentation and see how to write the tests themselves and add everything to classes like this:
// /test/unit/lib/yourPluginClassTest.php
The above example will test your method yourPluginClass::yourMethod() so you see the conventions of appending "Test" to the class and prepending "test" to the methods your going to test.
I found it a good practice to organise my test cases the same way as the tested classes.
For example:
/lib/routing/yourOwnRoute.class.php (classname: yourOwnRoute)
would be tested in:
/test/unit/lib/routing/yourOwnRouteTest.php (classname: yourOwnRouteTest)
Another good practice is to have one test case per class so each of your classes has exactly one test class.
You can run your test case like this.
$ phpunit /test/unit/lib/routing/yourOwnRouteTest.php
How to write a test suite?
Often you want to run all your test cases at once. In fact for continuous integration purposes this is always the case. This can be achieved using a PHPUnit test suite that can run multiple test cases.
A good naming convention is to create such a suite in the /test directory of your plugin and to call it like you called you plugin.
// /test/yourPluginTests.php
getPluginConfiguration('yourPlugin');
// instantiate a fake symfony unit test task to retrieve all connected tests for this plugin
$task = new sfTestUnitTask($configuration->getEventDispatcher(), new sfFormatter());
$event = new sfEvent($task, 'task.test.filter_test_files', array('arguments' => array('name' => array()), 'options' => array()));
$files = $pluginConfig->filterTestFiles($event, array());
$suite->addTestFiles($files);
return $suite;
}
}
In this example I collected all test cases from the fixture project just like the symfony test task for lime does it. This is very convenient as you don't have to manually add new test cases. Refer to the AllTests section of the PHPUnit documentation for further details of a test suite.
Next we need to make all our test cases known to the fixture project by editing the ProjectConfiguration class.
// taken from http://trac.symfony-project.org/browser/plugins/sfImageTransformExtraPlugin/trunk/test/fixtures/project/config/ProjectConfiguration.class.php
pluginConfigurations['sfImageTransformExtraPlugin']->connectTests();
}
}
Now you can run all your test cases like this.
$ phpunit /test/yourPluginTests.php
The fixture project
I already mentioned the dependency towards symfony but there is more. There is also the dependency of your plugin towards being properly installed.
This dependency has to be reflected in the fixture project.
For example if you have any schema defined for Doctrine or Propel then the classes generated from these schema will be generated outside your plugin except for the classes prefixed with "Plugin". So you have to create these generated classes inside of the fixture project as well.
Make sure not to modify these classes they have to be absolutely pristine!
If your plugin needs an application youn have to create one in the fixtrue project and if certain settings in the app.yml or setting.yml or elsewhere are mandatory you will have to create them in the fixture project as well.
Just be aware that you don't try to fix anything by only adding more to the fixture project. There should be as few changes to it as possible and all changes you have made should be properly documented as they are necessary for anybody who wants to use your plugin.
Dependencies to other plugins
Sometimes your plugin will depend on the existence of other plugins. For sfImageTransformExtraPlugin this was the case as it required sfImageTransformPlugin to be installed. In your fixture project this can be solved by editing the ProjectConfiguration class.
// taken from http://trac.symfony-project.org/browser/plugins/sfImageTransformExtraPlugin/trunk/test/fixtures/project/config/ProjectConfiguration.class.php
setPlugins(array('sfImageTransformExtraPlugin', 'sfImageTransformPlugin', 'sfDoctrinePlugin'));
$this->setPluginPath('sfImageTransformExtraPlugin', dirname(__FILE__).'/../../../..');
$this->setPluginPath('sfImageTransformPlugin', dirname(__FILE__).'/../../../../../sfImageTransformPlugin');
}
// ...
}
This expects sfImageTransformPlugin to be located right next to sfImageTransformExtraPlugin which comes in handy if integrated with continuous integration services.
What to test?
For quite some time I kept asking this question. My answer is this:
- Don't test module code! Module code in this case means actions or components. They have far too many dependencies in symfony 1.x and they should be very slim anyway. Test them with functional tests.
- Don't test tests! This should be self explaining really..
- Test everything else! This leaves you with the
/liband/configfolders only.
And to keep a good overview you should create a coverage report from time to time. Using the following configuration file I use to create mine.
./test/unit/
./lib/
./config/
Change into the root directory of your plugin to execute phpunit other wise the relative paths will not work as they are relative to the execution dir not the configuration file!
Conclusion
PHPUnit testing of symfony plugins is tricky as it is not documented anywhere. But it is still very possible and once you got the hang of it feels very natural.
I hope this will help some of you t get started.
Things like mocking database connections or all the hazzle with sfContext might be subject to another post.
Bootstrap · Continuous Integration · phpUnit · sfImageTransformExtraPlugin · symfony · Tests · Unit Tests · xUnit
<< sfImageTransformExtraPlugin 1.0.0 finally released stable!


Pingback: Tweets that mention New article: #Best #practice of #PHPUnit #testing a #symfony 1.4 #plugin #in -- Topsy.com
Pingback: • Noch mehr Erfahrungen mit PHPUnit Testing von symfony Plugins | test.ical.ly
Pingback: • Bootstrap Besonderheiten für Unit Tests von symfony Plugins – Teil 2 | test.ical.ly
Pingback: uberVU - social comments
Pingback: Test.ical.ly Blog: Best practice of PHPUnit testing a symfony 1.4 plugin | Development Blog With Code Updates : Developercast.com
Pingback: Webs Developer » Test.ical.ly Blog: Best practice of PHPUnit testing a symfony 1.4 plugin
Pingback: • Best practice of PHPUnit testing a symfony 1.4 plugin | test.ical.ly | Programming Blog Imagik.org
Pingback: • Running PHPUnit tests for your symfony plugin in phpUnderControl | test.ical.ly