BDD basics with PHPSpec
BDD is a technique used at story level and spec level. The technique is to first describe the behaviour of an object you are about to write. Next you write just enough code to meet that specification.
Sounds interesting. Let’s give it a shot, shall we?
We’re going to start off creating a fresh project using the Symfony Skeleton, and installing PHPSpec. Using the symfony binary, creating a new project is very straight forward. You can follow along if you like.
I like motorcycles, so let’s use that as our subject.
First of all, we’ll need to create the specification to describe our motorcycle class. Luckily, PHPSpec can help us create this file.
PHPSpec will now generate a specification for the
App/Motorcycle class in the
The generated file will look like this:
As you can see, this doesn’t describe much. The method
it_is_initializable will just check if the Motorcycle class can be initialized. Currently, we haven’t created that class yet - so running PHPSpec should give us an error.
We can run the tests using the following command.
As expected, PHPSpec tells us our code is broken. It also suggests creating the class for us, so we’ll accept that offer.
Note: You can disable the suggestions by passing the
--no-code-generation option when running the command.
We’ve now got our base class:
This also means our test should pass, so let’s try!
Great. As you noticed, PHPSpec helped us out quite a bit here. As you’ll see in the rest of this post, PHPSpec can actually generate quite a lot of stuff to help us. Let’s start adding some methods to our Motorcycle class.
First custom behaviour
We’re going to start off with a simple behaviour. For example, we want to make sure our motorcycle is currently running. We can add a fairly simple test for that to our specification.
Strange. We’re calling the
isRunning() method, but it doesn’t exist in our specification. Surprise! There’s a catch. The
$this keyword in the specification doesn’t refer to the spec itself, but to our Motorcycle class. Therefore, PHPSpec will try calling the function on the Motorcycle class.
We’re also chaining the
shouldReturn function. This is a PHPSpec Matcher. Matchers in PHPSpec are similar to assertions in PHPunit, and describe how an object should behave. There’s a lot of matchers available, to match your usecase.
We can try running our test again.
The failing test is obviously the
All tests green looked way better, so let’s fix it.
In our Motorcycle class, we’ll add a method
isRunning() that returns true.
This immediately fixes our tests, as expected.
Slightly more advanced testing (Stubs)
Class methods often require arguments to perform actions. Our motorcycle, for example, could have a function
canRide(Rider $rider), that returns whether or not the rider can ride the motorcycle.
To test this, we’ll add the following stub:
Our tests are failing, but as you can see, PHPSpec suggests generating an interface
Perhaps we’ll want to add different types of people later, so using an interface makes sense. Let’s say no for now, and replace our Rider typehint with PersonInterface. This way we can make sure our naming is nice and clean.
We’ll run the PHPSpec command again, and allow it to generate the interface for us. Okay, so now that we’ve got the interface, will our test pass?
Obviously not. Our interface doesn’t even have the
hasMotorcycleLicense() method signature.
Again, PHPSpec suggests generating it, so let’s agree.
Sweet! We’ve now got our interface ready.
Note that in the code snippet below, I did add the
bool return type myself.
All we need to do now, is implement the
canRide method in the Motorcycle class.
When we try running PHPSpec again, we can once again see that all our tests are green!
The functionalities I’ve shown here are just the tip of the iceberg. Perhaps I can do a follow-up with some more advanced examples, but for now, this is the end. I hope you’ve enjoyed this read, and hopefully you’ve even learned something along the way. I highly suggest that you check out the PHPSpec Manual if you’re interested in learning more.
- Symfony 5.1
- PHPSpec 6.2