This is the second part of a series about Outside-in Behaviour Driven Development in PHP. The first part introduces outside-in development, and how to execute scenarios with Behat. Read this to catch up with the tools and the example we've used so far, then come back to find out how PHPSpec fits into this picture.
PHPSpec is the first ever PHP BDD framework. It is a port of RSpec to PHP created back in 2007 by Pádraic Brady and Travis Swicegood. Development in this framework stopped for a while and was reignited last August (2010). PHPSpec can be installed via pear, using these commands:
$ sudo pear config-set preferred_state beta $ sudo pear channel-discover pear.phpspec.net $ sudo pear install --all-deps phpspec/PHPSpec |
If you are accustomed to unit testing, here is a quick translation sheet for xUnit/xSpec terms:
- In xUnit we test; in xSpec we describe, so your class names begin with "Describe"
- In PHPSpec, the spec file for a class MyClass is named MyClassSpec.php
- In xUnit we group tests in a TestCase. In xSpec we have examples that are grouped into Contexts
- In xUnit each method is a test with the prefix test. In xSpec each method is an example, we use the prefix it
- In xUnit we assert that something work as expected. In xSpec we specify how it should work
We've used Gherkin and Behat to specify how our application is supposed to work. We use PHPSpec to specify the behaviour of our classes. To get you started, let's see a simple example showing this. We are building a class that greets the user with "Hello, World!". The GreeterSpec.php file would look like this:
class DescribeGreeter extends PHPSpecContext { function before() { $this->greeter = $this->spec(new Greeter); } function itGreetsUsingAHelloWorldMessage() { $message = $this->greeter->greet(); $message->should->be('Hello, World!'); } } |
If you already have experience with Unit Testing, the above would look familiar to you. Note that instead of saying: $this->assertEquals('Hello, World!', $message) we say $message->should->be('Hello, World!'). We are describing how we want the behaviour to be, rather than testing it. The before() method is a setup method that would get run before any example is run; the spec() method is a decorator that wraps the object being tested so we call the expectations on the properties and methods results.
To run the spec we use the phpspec command:
$ phpspec GreeterSpec.php |
First we run it and watch it fail. Then we write a Greeter class to satisfy the example.
PHPSpec and MVC
Going back to our Video Renting application. The newly released version (1.2beta) includes integration with Zend Framework which enables us to test the MVC (Model, View, Controller) components individually. When we test an MVC application, we should start with the view, because that's what our scenario describes:
The text "Revolution OS" was not found anywhere in the text of the current page
Let's keep all our specs in a folder called "spec". To write a view spec we first add a folder called views under the spec folder. In our example, this is a view that corresponds to the index action of the review controller. We need to create an IndexSpec.php for our index.phtml view, containing the following code:
namespace Review; // SpecHelper contains the usual ZF bootstrap // copied from public/index.php require_once __DIR__ . '/../../SpecHelper.php'; use PHPSpecContextZendView as ViewContext; class DescribeIndex extends ViewContext { function itRendersTheSelectedVideo() { $video = Mockery::mock( 'Application_Model_Video', array('getName' => 'Revolution OS')); $this->assign('video', $video); $this->render(); $this->rendered->should->contain('Revolution OS'); } } |
Notice that we namespaced our class with the name of the controller. In a modular Zend Framework application you would namespace with the module name and then controller name, e.g. ModuleController.
If we run the spec we should see an error because we haven't met its description yet.
$ phpspec spec E Exceptions: 1) ReviewIndex renders the selected video FailureException: $this->runExamples($exampleGroup, $reporter); Zend_View_Exception: script 'review/index.phtml' not found in path (/private/var/www/renting/application/views/scripts/) Finished in 0.004656 seconds 1 example, 1 exception |
At this stage we need to create the view to deal with the error we saw. In this example, we will use Zend Tool, which creates the controller/action/view all in one go:
$ zf create controller Review Creating a controller at /private/var/www/renting/application/controllers/ReviewController.php Creating an index action method in controller Review Creating a view script for the index action method at /private/var/www/renting/application/views/scripts/review/index.phtml Updating project profile '/private/var/www/renting/.zfproject.xml' |
Now instead of an error, our output shows that we have a failure:
$ phpspec spec F Failures: 1) ReviewIndex renders the selected video FailureError: $this->rendered->should->contain('Revolution OS'); expected to contain 'Revolution OS', found no match (using contain()) # ./spec/views/review/IndexSpec.php:17 Finished in 0.005078 seconds 1 example, 1 failure |
This output means that the view now exists, but it's not showing the described text. That's what we expect, since we haven't coded the view yet. Let's add some code to do so:
<?php echo $this->video->getName() ?> |
It should now pass.
$ phpspec spec . Finished in 0.001043 seconds 1 example |
That's progress! However Behat is still not happy, because our controller is not setting the model for the view yet. Let us turn our focus to the controller, and start by creating a controllers directory and adding a ReviewControllerSpec.php.
DescribeReviewController must extend PHPSpecContextZendController. We also need to specify that our controller will send be accessed by POST and will create the model in it. The controller/action will be routed from "/review" and we can add an example to make sure the route work as expected.
require_once __DIR__ . '/../SpecHelper.php'; class DescribeReviewController extends PHPSpecContextZendController { function itShouldRouteTheReviewsPageToTheIndexAction() { $this->routeFor(array('controller' => 'review','action' => 'index')) ->should->be('/review'); } function itShouldDispatchToTheReviewController() { $container = new Yadif_Container( new Zend_Config_Xml(APPLICATION_PATH . "/configs/objects.xml") ); $mapper = Mockery::mock('Application_Model_VideoMapper'); $mapper->shouldReceive('find')->andReturn($container->videoModel)->with('1')->once(); $container->videoMapper = $mapper; $this->_getZendTest()->bootstrap->getBootstrap()->setContainer($container); $this->post('/review', array('id' => '1')); } } |
We'll also want to add the code for the controller itself. Assuming you are using Yadif or another IoC (Inversion of Control) container, it would look like this:
class ReviewController extends Zend_Controller_Action { public function indexAction() { $this->view->video = $this->getMapper()->find($this->_request->id); } function getMapper() { $container = $this->getInvokeArg('bootstrap')->getContainer(); return $container->getComponent('videoMapper'); } } |
We'll see that this now passes, when we run:
$ phpspec spec ... Finished in 0.005271 seconds 3 examples |
This looks much healthier, but if we run Behat, it will still be unhappy. We need to pass real data to the view, and then Behat will point out that the next step now is to describe the model. We need to have the data of our selected movies so we can fetch our view properly. Testing the models should be focused on behaviour, rather than the mechanics of database access. We know Zend_Db works, we need to know if we have made mistakes in our model. We will therefore test for the validation, filtering and business rules that we keep in the model.
class DescribeVideo extends PHPSpecContext
{
function before()
{
$this->video = $this->spec(new Application_Model_Video);
}
function itIsNotValidWithoutTheName()
{
$this->video->setName('');
$this->video->shouldNot->beValid();
}
} |
We need to implement a isValid() method in the Video model. PHPSpec does not have a beValid matcher, but it will use predicate matchers and find a method isValid() by magic. Once the isValid() is implemented properly than the spec passes. You can do something similar if the form validation is stored in your (zend) forms. Also note the before() method that will be called before any example is run.
You can describe your mapper's behaviour by inspecting that it calls the Data Access Object (DAO) to fetch to or persist data from the models. At that point you can then add the DAO (e.g. DbTable, Rss, etc).
Should we Hit the Database?
In many cases you simply need to verify the business logic and not the database operation. Hitting the database consumes both time and resources, which will slow down the execution. There will be times where we need to expose some database behaviour or just feel more confident that our models work. In those cases we can use a Test Data Builder pattern (this is a topic on its own, and I will save it for another post).
If you run Behat again after the model specs pass, the view should be displaying the correct data, so your scenario should pass, and a new scenario will be failing. We use Behat to tell us what to do next, and so the outside-in cycle is begins again.
Final Thoughts
In BDD, developers are driven by the specification, rather than tests of things they haven’t written yet. That said, the goals of both TDD and BDD are basically the same: making sure the user gets what they want. BDD makes that focus more explicit, and uses a language that invites the user to write their tests as a specification.
Behat and PHPSpec sit at different levels in the Outside-in cycle. Behat provides the outermost layer, allowing the stakeholders and developers to collaborate. PHPSpec provides the inner layer, allowing the specification of how the classes will collaborate with each other.

12 comments












12 Responses
Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.
Marcello, thank you for this series--It is really inspiring me to think about my testing in a whole new way.
The fact that your examples are starting to integrate with Zend Framework is just icing on the cake!
I can't hardly wait to make some free time and dig in!
Thanks for the article!
I'm wondering if views should really be tested - while it may be good practice, most of the time they don't contain complex logic but rather simply output things. What arguments do you have in favor of testing views?
Hi Gabriel.
Thanks for your good question.
BDD means starting from the point of view of the user. Specifying your views first is actually a quite powerful way to keep it simple and effective. Because what comes next will need to satisfy what the views need, it also helps keeping the design of the rest of the code base to the point.
There is a general argument on where do you "test" your views. Do you verify it at acceptance level or lower level? The answer is it depends. But it's good to keep in mind one thing. For some applications the UI can change quite considerably from release to release. You don't want to change your selenium tests every time, believe me, that's very painful.
View specs makes the change more flexible and easy to do, and fits in with the TDD/BDD sustainable pace idea. You know what you need to do next.
PHPSpec - the first PHP BDD Framework? What about Behat? http://behat.org/
Behat started development a long time ago. Everzet told me that it started even before cuke4php. But release 0.1.0 is dated September 7th, 2010 (https://github.com/Behat/Behat/commit/395a5ee5f0dc0bb1026add166fe88598687a8624). PHPSpec development started September 2007 and the first release is dated November 2007 (that was version 0.2.0).
Having said that, this does not mean one framework is better than another. Nor does it mean Everzet should not start a BDD framework since another already existed. The frameworks serve 2 different purposes, as we outlined in these articles. And I am very glad that Everzet has chosen to do so. Behat is an amazing project.
just had the time to go briefly over this.
I think the messing with Zend threw me off completely since i am not so familiar with it on the first reading. I would want a more neutral example. It is not clear how TDD evolves since you enter another framework and confuse things. I guess it goes V->C->M in that order and you go one by one.
For what i understood and blogged about:
Scen BDD --> Spec BDD --> Some automation-> Class implementation
the outter is BDD is set to red
then we go and create a spec BDD to help pass our Scen BDD
then initially we get a red Spec BDD
then we try minimum outside-in effort to make it green
then we go in again and so on until we get a green
The result is that we implement and refactor our classes
What throws me off is that it seems you have some PHPSpec framework specific classes, that is for zend...
It just throws me off. I need to go over this with my own tools to be able to see it properly. Thanks though. Also you eat a lot again on the last steps, that is pretty bad as it assumes a lot of your expertise on the reader. It is good if you explain with more details like a tutorial. The video example sucks in a way because it puts more info to think about...
suggestion:
A video? simpler example, code available on github? sounds like a plan..
Thanks!
just found this https://github.com/MarcelloDuarte/phpspec-zend
hmm still it is too dependent. I will have to research this library to adapt it to my framework and work flow...
Continuing the Discussion
BDD - http://techportal.ibuildings.com/2011/08/03/outside-in-behaviour-driven-development-in-php-part-2/
Beyond TDD with PHPSpec - TechPortal http://tinyurl.com/3gxxx2x