Behavior & Specification
Driven Development in PHP
Presented by Joshua Warren
Slide 2
Slide 2 text
OR:
Slide 3
Slide 3 text
I heard you like to code, so let’s
write code that writes code while
you code.
Slide 4
Slide 4 text
No content
Slide 5
Slide 5 text
About Me
Slide 6
Slide 6 text
PHP Developer
Working with PHP since 1999
Slide 7
Slide 7 text
Founder & CEO
Founded Creatuity in 2008
PHP Development Firm
Focused on the Magento platform
Tink, a Creatuity shareholder
Slide 8
Slide 8 text
JoshuaWarren.com
@JoshuaSWarren
Slide 9
Slide 9 text
IMPORTANT!
• joind.in/14065
• Download slides
• Post comments
• Leave a rating!
Slide 10
Slide 10 text
What You Need To Know
ASSUMPTIONS
Slide 11
Slide 11 text
Today we assume you’re a PHP developer.
Slide 12
Slide 12 text
That you are familiar with test driven development.
Slide 13
Slide 13 text
And that you’ve at least tried PHPUnit, Selenium or
another testing tool.
Slide 14
Slide 14 text
BDD - no, the B does not stand for beer, despite what a Brit might tell you
Behavior Driven Development
Slide 15
Slide 15 text
Think of BDD as stepping back a level from TDD.
Slide 16
Slide 16 text
Graphic thanks to BugHuntress
Slide 17
Slide 17 text
TDD generally deals with functional units.
Slide 18
Slide 18 text
BDD steps back a level to consider complete features.
Slide 19
Slide 19 text
In BDD, you write feature files in the form of user stories
that you test against.
Slide 20
Slide 20 text
BDD uses a ubiquitous language - basically, a language
that business stakeholders, project managers, developers
and our automated tools can all understand.
Slide 21
Slide 21 text
Sample Behat Feature File
Feature: Laravel Test
In order to demonstrate Laravel and Behat
As a user
I need to be able to visit the homepage of a new Laravel app
Scenario: Homepage
Given I am on the homepage
Then I should see "Laravel 5"
Slide 22
Slide 22 text
Behat
Slide 23
Slide 23 text
We implement BDD in PHP with a tool called Behat
Slide 24
Slide 24 text
Behat is a free, open source tool designed for BDD
and PHP
Slide 25
Slide 25 text
behat.org
Slide 26
Slide 26 text
SpecBDD - aka, Testing Tongue Twisters
Specification Behavior Driven
Development
Slide 27
Slide 27 text
Before you write a line of code, you write a
specification for how that code should work
Slide 28
Slide 28 text
Focuses you on architectural decisions up-front
Slide 29
Slide 29 text
PHPSpec
Slide 30
Slide 30 text
Open Source tool for specification driven
development in PHP
Slide 31
Slide 31 text
www.phpspec.net
Slide 32
Slide 32 text
Why Use Behat and PHPSpec?
Slide 33
Slide 33 text
These tools allow you to focus exclusively on logic
Slide 34
Slide 34 text
Helps build functional testing coverage
Slide 35
Slide 35 text
Guides planning and ensuring that all
stakeholders are in agreement
Slide 36
Slide 36 text
Let’s Build Something!
Slide 37
Slide 37 text
… what we’re building …
Slide 38
Slide 38 text
Setting up Our Project
Slide 39
Slide 39 text
Setup a Laravel 5 project
Slide 40
Slide 40 text
Run composer require —dev
behat/behat
behat/mink
behat/mink-extensions
laracasts/behat-laravel-extension
phpspec/phpspec
benconstable/phpspec-laravel
features/fitbit.feature
Feature: Fitbit Integration
In order to obtain Fitbit data
As a user
I need to be able to authenticate with Fitbit
Scenario: Not yet authenticated
Given I am not logged in as “[email protected]”
When I go to "/fitbit/"
Then I should see "Please authenticate"
Slide 46
Slide 46 text
vendor/bin/behat —append-snippets
Scenario: Not yet authenticated:6
Given I am not logged in as “[email protected]
When I go to "/fitbit/"
Then I should see "Please authenticate"
1 scenario (1 undefined)
3 steps (1 undefined, 2 skipped)
0m0.48s (11.00Mb)
u features/bootstrap/FeatureContext.php - `I am not logged in as`
definition added
Slide 47
Slide 47 text
Behat’s written code for us!
Slide 48
Slide 48 text
/features/bootstrap/FeatureContext.php
/**
* @Given I am not logged in as :arg1
*/
public function iAmNotLoggedInAs($arg1)
{
throw new PendingException();
}
Slide 49
Slide 49 text
Behat writes just enough to get us show us where
to add our logic.
Slide 50
Slide 50 text
Behat expects us to add logic to this function to
detect the user is not logged in.
Slide 51
Slide 51 text
Before we do that, let’s finish out our feature file.
Slide 52
Slide 52 text
features/fitbit.feature continued
Scenario: I have authenticated
Given I am logged in as “[email protected]”
When I go to "/fitbit/"
Then I should see "Welcome back"
Scenario: I have sleep data
Given I am logged in as “[email protected]”
When I go to "/fitbit/sleep/"
Then I should see "Sleep Report"
Slide 53
Slide 53 text
Run vendor/bin/behat —append-snippets one
more time
Slide 54
Slide 54 text
Now, let’s fill in the logic Behat needs us to add.
Slide 55
Slide 55 text
/features/bootstrap/FeatureContext.php
/**
* @Given I am not logged in as :email
*/
public function iAmNotLoggedInAs($email)
{
// We completely log out
// Destroy the previous session
if (Session::isStarted()) {
Session::regenerate(true);
} else {
Session::start();
}
}
Slide 56
Slide 56 text
/features/bootstrap/FeatureContext.php
public function iAmLoggedInAs($email)
{
// Destroy the previous session
if (Session::isStarted()) {
Session::regenerate(true);
} else {
Session::start();
}
// Login the user and since the driver and this code now
// share a session this will also login the driver session
$user = User::where('email', $email)->firstOrFail();
Auth::login($user);
// Save the session data to disk or to memcache
Session::save();
// Hack for Selenium
// Before setting a cookie the browser needs to be launched
if ($this->getSession()->getDriver() instanceof \Behat\Mink\Driver\Selenium2Driver) {
$this->visit('login');
}
// Get the session identifier for the cookie
$encryptedSessionId = Crypt::encrypt(Session::getId());
$cookieName = Session::getName();
// Set the cookie
$minkSession = $this->getSession();
$minkSession->setCookie($cookieName, $encryptedSessionId);
}
Slide 57
Slide 57 text
We run vendor/bin/behat once more
Slide 58
Slide 58 text
vendor/bin/behat
…
Scenario: I have sleep data
Given I am logged in as "[email protected]"
When I go to "/fitbit/sleep/"
Then I should see "Sleep Report"
The text "Sleep Report" was not found anywhere in the text of
the current page. (Behat\Mink\Exception\ResponseTextException)
--- Failed scenarios:
features/fitbit.feature:6
features/fitbit.feature:11
features/fitbit.feature:16
Slide 59
Slide 59 text
A perfect failure!
Slide 60
Slide 60 text
These failures show us that Behat is testing our
app properly, and now we just need to write the
application logic.
Slide 61
Slide 61 text
Specifications
Slide 62
Slide 62 text
Now we write specifications for how our Fitbit class
should work.
Slide 63
Slide 63 text
These specifications should provide the logic to
deliver the results that Behat is testing for.
Slide 64
Slide 64 text
vendor/bin/phpspec describe Fitbit
Specification for Fitbit created in /spec/
FitbitSpec.php
Slide 65
Slide 65 text
PHPSpec generates a basic spec file for us
Slide 66
Slide 66 text
spec/FitbitSpec.php
namespace spec;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class FitbitSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType('Fitbit');
}
}
Slide 67
Slide 67 text
This default spec tells PHPSpec to expect a class
named Fitbit.
Slide 68
Slide 68 text
Now we add a bit more to the file so PHPSpec will
understand what this class should do.
Slide 69
Slide 69 text
spec/FitbitSpec.php continued
function it_connects_to_fitbit($email)
{
$this->connect($email)->shouldReturn('Success');
}
function it_returns_sleep_data($email)
{
$this->sleepData($email)->shouldReturn([8, 8, 8, 8, 8]);
}
Slide 70
Slide 70 text
Now we run PHPSpec once more…
Slide 71
Slide 71 text
vendor/bin/phpspec run
10 ! is initializable (142ms)
class Fitbit does not exist.
15 ! connects to fitbit (100ms)
class Fitbit does not exist.
20 ! returns sleep data
class Fitbit does not exist.
---- broken examples
Fitbit
10 ! is initializable (142ms)
class Fitbit does not exist.
Fitbit
15 ! connects to fitbit (100ms)
class Fitbit does not exist.
Fitbit
20 ! returns sleep data
class Fitbit does not exist.
1 specs
3 examples (3 broken)
Slide 72
Slide 72 text
Lots of failures…
Slide 73
Slide 73 text
But wait a second - PHPSpec prompts us!
Slide 74
Slide 74 text
Do you want me to create `Fitbit` for you?
[Y/n]
Slide 75
Slide 75 text
PHPSpec will create the class and the methods for us!
Slide 76
Slide 76 text
This is very powerful with frameworks like Laravel
and Magento, which have PHPSpec plugins that
help PHPSpec know where class files should be
located.
Slide 77
Slide 77 text
Fitbit.php - class Fitbit {
function connect($email)
{
// TODO: write logic here
}
function sleepData($email)
{
// TODO: write logic here
}
Slide 78
Slide 78 text
And now, the easy part…
Slide 79
Slide 79 text
Implementation
Slide 80
Slide 80 text
Implement logic in the new Fitbit class in the
locations directed by PHPSpec
Slide 81
Slide 81 text
Tie that logic into views in our application.
Slide 82
Slide 82 text
Once we’re done with the implementation, we
move on to…
Slide 83
Slide 83 text
Testing
Slide 84
Slide 84 text
Once we’re done, running phpspec run should
return green
Slide 85
Slide 85 text
Once phpspec returns green, run behat, which
should return green as well
Slide 86
Slide 86 text
We now know that our new feature is working
correctly without needing to open a web browser
Slide 87
Slide 87 text
PHPSpec gives us confidence that the application
logic was implemented correctly.
Slide 88
Slide 88 text
Behat gives us confidence that the feature is being
displayed properly to users.
Slide 89
Slide 89 text
Success!
Slide 90
Slide 90 text
The purpose of this talk is to get you hooked on
Behat & PHPSpec and show you how easy it is to
get started.
Slide 91
Slide 91 text
Behat and PHPSpec are both powerful tools
Slide 92
Slide 92 text
PHPSpec can be used at a very granular level to
ensure your application logic works correctly
Slide 93
Slide 93 text
Next week, setup Behat and PHPSpec on one of
your projects and take it for a quick test by
implementing one short feature.
Slide 94
Slide 94 text
Keep In Touch!
• joind.in/14065
• @JoshuaSWarren
• JoshuaWarren.com