Working With Behat
I'm always looking for ways to make my code better. One of the things I fully believe in is that everytime you make something you should be learning something while doing it. So, when I sat down to rewrite this site, I wanted to learn how to use Behaviour Driven Development (BDD) to make the site do what I wanted it to do.
The key difference, in my eyes at least, between BDD and TDD (Test Driven Development) is that we describe how we want to do something - create a blog post, sort out tagging, log in etc - instead of testing the mechanisms that actually do that sort of thing individually.
It's quite beautiful to behold when you compare the test suite to my personal site compared to the test suite used elsewhere. Compare the following
Feature: Login
As a user
I can log in
To update the site
Background: There is a user
Given there is a user
Scenario: I can log in with valid information
Given I am on "/login"
And I fill in my login details
Then I should see a flash message "Login successful!"
And I should be logged in
Scenario: I can't log in without valid information
Given I am on "/login"
And I fill in invalid login details
Then I should not be logged in
And I should be on "/login"
And I should see a flash message "Incorrect username/password"
Scenario: I can log out
Given I am logged in
And I am on "/logout"
Then I should not be logged in
And I should see a flash message "You are now logged out"
To something like:
class HelpersTest extends BootstrapperWrapper
{
public function testOutputCorrectCSSLinks() {
$css = Helpers::get_CSS();
$this->assertEquals("<link rel='stylesheet' href='//netdna.bootstrapcdn.com/bootstrap/3.1.0/css/bootstrap.min.css'><link rel='stylesheet' href='//netdna.bootstrapcdn.com/bootstrap/3.1.0/css/bootstrap-theme.min.css'>", $css);
}
public function testOutputCorrectJSLinks() {
$js = Helpers::get_JS();
$this->assertEquals("<script src='http://code.jquery.com/jquery-2.1.0.min.js'></script><script src='//netdna.bootstrapcdn.com/bootstrap/3.1.0/js/bootstrap.min.js'></script>", $js);
}
}
Now obviously, this sort of thing isn't perfect. I'd much rather use unit tests and TDD when I work with the Bootstrapper package. But on the other hand, I'm building a website. I'm making something which people need to digest, that people need to interact with. A unit test doesn't really make that sort of thing work.
Behat
So, while I've been working with BDD I've been using a package called Behat. It was built to provide PHP developers the same sort of thing that Cucumber provides Ruby developers*. It means that when I think about the new feature I want to implement, I can sit down and think "Okay, how do I actually want this to work?".
Why is this important? It starts you thinking in a much cleaner way that just doing a unit test. Every step that you define is there because an actual user told you that's how they wanted it to work. Some of the features I've defined and the steps that I follow are because I know who the user is (me), and I feel confident that I can say "I'm going to go to /blog/create" because that's what I would do automatically - I'm confident in my abilities to play about with URIs.
It makes a big difference from a coding perspective. It causes a couple of problems when you start out - if creating a blog post fails, I actually don't know if the problem was because my validator failed or the storage method failed or any other myriad of things - but I'm now confident that I built something usable.
Tips and tricks
So, here's a couple of tips and tricks I'd suggest using. These are mostly Laravel specific, but you can probably chop and change things if you're using something else.
- Import your MVC stack (or at the very least, your Composer Autoload file). For Laravel apps, you need to add something akin to
require_once __DIR__ . "/../../../../bootstrap/start.php";
(assuming that you have Behat features insideapp/tests/acceptance
). Why? Because then you can all sorts of sugar that make your features run a lot faster. - Use the
@BeforeFeature
and@AfterFeature
annotations to clear the database or whatever before and after each test. I would suggest using transactions, but I've not found it to work while I'm using Behat. I've been usingArtisan::call("migrate:refresh")
to rollback all the migrations and then re-run them. If you work out how to get transactions to work properly then do send me a message. - Use things like Faker to speed up your features. The first version of my features took about 2 minutes to run. That is awful in so many ways. The current version takes 30 seconds to run. It's not perfect, but it works for now. Using steps like
Given there is a blog post with title "Editing Test" and content "I made a boo boo"
and creating the post directly inside the database is much faster than using a step likeGiven I create a blog post with title "Foo" and content "Baring all the baz"
since that'll have to do the entire request process.
* Side note: You can actually use Cucumber with anything. The problem is that the step definitions - the bit that turns I fill in my login information
into actual steps the program can follow - have to be written in Ruby. I like Ruby, but I also like staying in the same language from minute to minute if at all possible.