Slide 1

Slide 1 text

PHPSpec & Behat: Two Testing Tools That Write Code For You Presented by Joshua Warren

Slide 2

Slide 2 text

OR:

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

I heard you like to code, so let’s write code that writes code while you code.

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

Slide 8

Slide 8 text

JoshuaWarren.com @JoshuaSWarren

Slide 9

Slide 9 text

IMPORTANT! • joind.in/15623 • 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 up a level from TDD.

Slide 16

Slide 16 text

Graphic thanks to BugHuntres

Slide 17

Slide 17 text

TDD generally deals with functional units.

Slide 18

Slide 18 text

BDD steps up 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: Up and Running
 In order to confirm Behat is Working
 As a developer
 I need to see a homepage
 
 
 Scenario: Homepage Exists
 When I go to "/bdd/"
 Then I should see "Welcome to the world of BDD"


Slide 22

Slide 22 text

BDD gets all stakeholders to agree on what “done” looks like before you write a single line of code

Slide 23

Slide 23 text

Behat

Slide 24

Slide 24 text

We implement BDD in PHP with a tool called Behat

Slide 25

Slide 25 text

Behat is a free, open source tool designed for BDD and PHP

Slide 26

Slide 26 text

behat.org

Slide 27

Slide 27 text

SpecBDD - aka, Testing Tongue Twisters Specification Behavior Driven Development

Slide 28

Slide 28 text

Before you write a line of code, you write a specification for how that code should work

Slide 29

Slide 29 text

Focuses you on architectural decisions up- front

Slide 30

Slide 30 text

PHPSpec

Slide 31

Slide 31 text

Open Source tool for specification driven development in PHP

Slide 32

Slide 32 text

www.phpspec.net

Slide 33

Slide 33 text

Why Use Behat and PHPSpec?

Slide 34

Slide 34 text

These tools allow you to focus exclusively on logic

Slide 35

Slide 35 text

Helps build functional testing coverage quickly

Slide 36

Slide 36 text

Guides planning and ensuring that all stakeholders are in agreement

Slide 37

Slide 37 text

Why Not PHPUnit?

Slide 38

Slide 38 text

PHPSpec is opinionated - in every sense of the word

Slide 39

Slide 39 text

PHPSpec forces you to think differently and creates a mindset that encourages usage

Slide 40

Slide 40 text

PHPSpec tests are much more readable

Slide 41

Slide 41 text

Read any of Marcello Duarte’s slides on testing

Slide 42

Slide 42 text

What About Performance?

Slide 43

Slide 43 text

Tests that take days to run won’t be used

Slide 44

Slide 44 text

PHPSpec is fast

Slide 45

Slide 45 text

Behat supports parallel execution

Slide 46

Slide 46 text

Behat and PHPSpec will be at least as fast as the existing testing tools, and can be much faster

Slide 47

Slide 47 text

Enough Theory: Let’s Build Something!

Slide 48

Slide 48 text

We’ll be building a basic time-off request app.

Slide 49

Slide 49 text

Visitors can specify their name and a reason for their time off request.

Slide 50

Slide 50 text

Time off requests can be viewed, approved and denied.

Slide 51

Slide 51 text

Intentionally keeping things simple, but you can follow this pattern to add authentication, roles, etc.

Slide 52

Slide 52 text

Want to follow along or view the sample code?

Slide 53

Slide 53 text

Vagrant box: https:/ /github.com/joshuaswarren/bdd- box Project code: https:/ /github.com/joshuaswarren/bdd

Slide 54

Slide 54 text

Setting up Our Project

Slide 55

Slide 55 text

Setup a folder for your project

Slide 56

Slide 56 text

Use composer to install Behat, phpspec & friends

Slide 57

Slide 57 text

composer require behat/behat —dev

Slide 58

Slide 58 text

composer require behat/mink-goutte-driver —dev

Slide 59

Slide 59 text

composer require phpspec/phpspec —dev

Slide 60

Slide 60 text

We now have Behat and Phpspec installed

Slide 61

Slide 61 text

We also have Mink - an open source browser emulator/controller

Slide 62

Slide 62 text

Mink Drivers Goutte - headless, fast, no JS Selenium2 - requires Selenium server, slower, supports JS Zombie - headless, fast, does support JS

Slide 63

Slide 63 text

We are using Goutte today because we don’t need Javascript support

Slide 64

Slide 64 text

We’ll perform some basic configuration to let Behat know to use Goutte

Slide 65

Slide 65 text

And we need to let phpspec know where our code should go

Slide 66

Slide 66 text

Run: vendor/bin/behat —init

Slide 67

Slide 67 text

Create /behat.yml default:
 extensions:
 Behat\MinkExtension:
 base_url: http://192.168.33.10/
 default_session: goutte
 goutte: ~


Slide 68

Slide 68 text

features/bootstrap/FeatureContext.php use Behat\Behat\Context\Context;
 use Behat\Behat\Context\SnippetAcceptingContext;
 use Behat\Gherkin\Node\PyStringNode;
 use Behat\Gherkin\Node\TableNode;
 use Behat\MinkExtension\Context\MinkContext;
 
 /**
 * Defines application features from the specific context.
 */
 class FeatureContext extends Behat\MinkExtension\Context\MinkContext
 {
 
 }

Slide 69

Slide 69 text

Create /phpspec.yml suites:
 app_suites:
 namespace: App
 psr4_prefix: App
 src_path: app


Slide 70

Slide 70 text

Features

Slide 71

Slide 71 text

features/UpAndRunning.feature Feature: Up and Running
 In order to confirm Behat is Working
 As a developer
 I need to see a homepage
 
 
 Scenario: Homepage Exists
 When I go to "/bdd/"
 Then I should see "Welcome to the world of BDD"


Slide 72

Slide 72 text

Run: bin/behat

Slide 73

Slide 73 text

features/SubmitTimeOffRequest.feature Feature: Submit Time Off Request
 In order to request time off
 As a developer
 I need to be able to fill out a time off request form
 
 Scenario: Time Off Request Form Exists
 When I go to "/bdd/timeoff/new"
 Then I should see "New Time Off Request"
 
 Scenario: Time Off Request Form Works
 When I go to "/bdd/timeoff/new"
 And I fill in "name" with "Josh"
 And I fill in "reason" with "Attending a great conference"
 And I press "submit"
 Then I should see "Time Off Request Submitted"


Slide 74

Slide 74 text

features/SubmitTimeOffRequest.feature Feature: Submit Time Off Request
 In order to request time off
 As a developer
 I need to be able to fill out a time off request form
 
 Scenario: Time Off Request Form Exists
 When I go to "/bdd/timeoff/new"
 Then I should see "New Time Off Request"
 
 Scenario: Time Off Request Form Works
 When I go to "/bdd/timeoff/new"
 And I fill in "name" with "Josh"
 And I fill in "reason" with "Attending a great conference"
 And I press "submit"
 Then I should see "Time Off Request Submitted"


Slide 75

Slide 75 text

features/SubmitTimeOffRequest.feature Feature: Submit Time Off Request
 In order to request time off
 As a developer
 I need to be able to fill out a time off request form
 
 Scenario: Time Off Request Form Exists
 When I go to "/bdd/timeoff/new"
 Then I should see "New Time Off Request"
 
 Scenario: Time Off Request Form Works
 When I go to "/bdd/timeoff/new"
 And I fill in "name" with "Josh"
 And I fill in "reason" with "Attending a great conference"
 And I press "submit"
 Then I should see "Time Off Request Submitted"


Slide 76

Slide 76 text

features/SubmitTimeOffRequest.feature Feature: Submit Time Off Request
 In order to request time off
 As a developer
 I need to be able to fill out a time off request form
 
 Scenario: Time Off Request Form Exists
 When I go to "/bdd/timeoff/new"
 Then I should see "New Time Off Request"
 
 Scenario: Time Off Request Form Works
 When I go to "/bdd/timeoff/new"
 And I fill in "name" with "Josh"
 And I fill in "reason" with "Attending a great conference"
 And I press "submit"
 Then I should see "Time Off Request Submitted"


Slide 77

Slide 77 text

features/ProcessTimeOffRequest.feature Feature: Process Time Off Request
 In order to manage my team
 As a manager
 I need to be able to approve and deny time off requests
 
 Scenario: Time Off Request Management View Exists
 When I go to "/bdd/timeoff/manage"
 Then I should see "Manage Time Off Requests"
 
 Scenario: Time Off Request List
 When I go to "/bdd/timeoff/manage"
 And I press "View"
 Then I should see "Pending Time Off Request Details"
 
 Scenario: Approve Time Off Request
 When I go to "/bdd/timeoff/manage"
 And I press "View"
 And I press "Approve"
 Then I should see "Time Off Request Approved"
 
 Scenario: Deny Time Off Request
 When I go to "/bdd/timeoff/manage"
 And I press "View"
 And I press "Deny"
 Then I should see "Time Off Request Denied"

Slide 78

Slide 78 text

features/ProcessTimeOffRequest.feature Feature: Process Time Off Request
 In order to manage my team
 As a manager
 I need to be able to approve and deny time off requests

Slide 79

Slide 79 text

features/ProcessTimeOffRequest.feature Scenario: Time Off Request Management View Exists
 When I go to "/bdd/timeoff/manage"
 Then I should see "Manage Time Off Requests"
 
 Scenario: Time Off Request List
 When I go to "/bdd/timeoff/manage"
 And I press "View"
 Then I should see "Pending Time Off Request Details"

Slide 80

Slide 80 text

features/ProcessTimeOffRequest.feature Scenario: Approve Time Off Request
 When I go to "/bdd/timeoff/manage"
 And I press "View"
 And I press "Approve"
 Then I should see "Time Off Request Approved"
 
 Scenario: Deny Time Off Request
 When I go to "/bdd/timeoff/manage"
 And I press "View"
 And I press "Deny"
 Then I should see "Time Off Request Denied"

Slide 81

Slide 81 text

run behat: bin/behat

Slide 82

Slide 82 text

Behat Output --- Failed scenarios: features/ProcessTimeOffRequest.feature:6 features/ProcessTimeOffRequest.feature:10 features/ProcessTimeOffRequest.feature:15 features/ProcessTimeOffRequest.feature:21 features/SubmitTimeOffRequest.feature:6 features/SubmitTimeOffRequest.feature:10 7 scenarios (1 passed, 6 failed) 22 steps (8 passed, 6 failed, 8 skipped) 0m0.61s (14.81Mb)

Slide 83

Slide 83 text

Behat Output Scenario: Time Off Request Management View Exists When I go to “/bdd/timeoff/manage" Then I should see "Manage Time Off Requests" The text "Manage Time Off Requests" was not found anywhere in the text of the current page.

Slide 84

Slide 84 text

No content

Slide 85

Slide 85 text

These failures show us that Behat is testing our app properly, and now we just need to write the application logic.

Slide 86

Slide 86 text

Specifications

Slide 87

Slide 87 text

Now we write specifications for how our application should work.

Slide 88

Slide 88 text

These specifications should provide the logic to deliver the results that Behat is testing for.

Slide 89

Slide 89 text

bin/phpspec describe App\\Timeoff

Slide 90

Slide 90 text

PHPSpec generates a basic spec file for us

Slide 91

Slide 91 text

spec\TimeoffSpec.php namespace spec\App;
 
 use PhpSpec\ObjectBehavior;
 use Prophecy\Argument;
 
 class TimeoffSpec extends ObjectBehavior
 {
 function it_is_initializable()
 {
 $this->shouldHaveType('App\Timeoff');
 }
 }


Slide 92

Slide 92 text

This default spec tells PHPSpec to expect a class named Timeoff.

Slide 93

Slide 93 text

Now we add a bit more to the file so PHPSpec will understand what this class should do.

Slide 94

Slide 94 text

spec\TimeoffSpec.php function it_creates_timeoff_requests() {
 $this->create("Name", "reason")->shouldBeString();
 }
 
 function it_loads_all_timeoff_requests() {
 $this->loadAll()->shouldBeArray();
 }
 
 function it_loads_a_timeoff_request() {
 $this->load("uuid")->shouldBeArray();
 }
 
 function it_loads_pending_timeoff_requests() {
 $this->loadPending()->shouldBeArray();
 }
 
 function it_approves_timeoff_requests() {
 $this->approve("id")->shouldReturn(true);
 }
 
 function it_denies_timeoff_requests() {
 $this->deny("id")->shouldReturn(true);
 }

Slide 95

Slide 95 text

spec\TimeoffSpec.php function it_creates_timeoff_requests() {
 $this->create("Name", "reason")- >shouldBeString();
 }
 
 function it_loads_all_timeoff_requests() {
 $this->loadAll()->shouldBeArray();
 }

Slide 96

Slide 96 text

spec\TimeoffSpec.php function it_loads_a_timeoff_request() {
 $this->load("uuid")->shouldBeArray();
 }
 
 function it_loads_pending_timeoff_requests() {
 $this->loadPending()->shouldBeArray();
 }


Slide 97

Slide 97 text

spec\TimeoffSpec.php function it_approves_timeoff_requests() {
 $this->approve("id")->shouldReturn(true);
 }
 
 function it_denies_timeoff_requests() {
 $this->deny("id")->shouldReturn(true);
 }

Slide 98

Slide 98 text

Now we run PHPSpec once more…

Slide 99

Slide 99 text

Phpspec output 10 ✔ is initializable 15 ! creates timeoff requests method App\Timeoff::create not found. 19 ! loads all timeoff requests method App\Timeoff::loadAll not found. 23 ! loads pending timeoff requests method App\Timeoff::loadPending not found. 27 ! approves timeoff requests method App\Timeoff::approve not found. 31 ! denies timeoff requests method App\Timeoff::deny not found.

Slide 100

Slide 100 text

Lots of failures…

Slide 101

Slide 101 text

But wait a second - PHPSpec prompts us!

Slide 102

Slide 102 text

PHPSpec output Do you want me to create `App \Timeoff::create()` for you? [Y/n]

Slide 103

Slide 103 text

PHPSpec will create the class and the methods for us!

Slide 104

Slide 104 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 105

Slide 105 text

And now, the easy part…

Slide 106

Slide 106 text

Implementation

Slide 107

Slide 107 text

Implement logic in the new Timeoff class in the locations directed by PHPSpec

Slide 108

Slide 108 text

Implement each function one at a time, running phpspec after each one.

Slide 109

Slide 109 text

phpspec should be returning all green

Slide 110

Slide 110 text

Move on to implementing the front- end behavior

Slide 111

Slide 111 text

Using Lumen means our view/display logic is very simple

Slide 112

Slide 112 text

app\Http\route.php $app->get('/bdd/', function() use ($app) {
 return "Welcome to the world of BDD";
 });

Slide 113

Slide 113 text

app\Http\route.php $app->get('/bdd/timeoff/new/', function() use ($app) {
 if(Request::has('name')) {
 $to = new \App\Timeoff();
 $name = Request::input('name');
 $reason = Request::input('reason');
 $to->create($name, $reason);
 return "Time off request submitted";
 } else {
 return view('request.new');
 }
 });

Slide 114

Slide 114 text

app\Http\route.php $app->get('/bdd/timeoff/manage/', function() use ($app) {
 $to = new \App\Timeoff();
 if(Request::has('uuid')) {
 $uuid = Request::input('uuid');
 if(Request::has('process')) {
 $process = Request::input('process');
 if($process == 'approve') {
 $to->approve($uuid);
 return "Time Off Request Approved";
 } else {
 if($process == 'deny') {
 $to->deny($uuid);
 return "Time Off Request Denied";
 }
 }
 } else {
 $request = $to->load($uuid);
 return view('request.manageSpecific', ['request' => $request]);
 }
 } else {
 $requests = $to->loadAll();
 return view('request.manage', ['requests' => $requests]);
 }

Slide 115

Slide 115 text

app\Http\route.php $app->get('/bdd/timeoff/manage/', function() use ($app) {
 $to = new \App\Timeoff();
 if(Request::has('uuid')) {
 $uuid = Request::input('uuid');
 if(Request::has('process')) {
 $process = Request::input('process');
 if($process == 'approve') {
 $to->approve($uuid);
 return "Time Off Request Approved";
 } else {
 if($process == 'deny') {
 $to->deny($uuid);
 return "Time Off Request Denied";
 }
 } …

Slide 116

Slide 116 text

app\Http\route.php …
 } else {
 $request = $to->load($uuid);
 return view('request.manageSpecific', ['request' => $request]);
 } …

Slide 117

Slide 117 text

app\Http\route.php …
 } else {
 $requests = $to->loadAll();
 return view('request.manage', ['requests' => $requests]);
 }

Slide 118

Slide 118 text

Our views are located in resources\views \request\ and are simple HTML forms

Slide 119

Slide 119 text

Once we’re done with the implementation, we move on to…

Slide 120

Slide 120 text

Testing

Slide 121

Slide 121 text

Once we’re done, running phpspec run should return green

Slide 122

Slide 122 text

Once phpspec returns green, run behat, which should return green as well

Slide 123

Slide 123 text

We now know that our new feature is working correctly without needing to open a web browser

Slide 124

Slide 124 text

This allows us to flow from function to function as we implement our app, without breaking our train of thought.

Slide 125

Slide 125 text

PHPSpec gives us confidence that the application logic was implemented correctly.

Slide 126

Slide 126 text

Behat gives us confidence that the feature is being displayed properly to users.

Slide 127

Slide 127 text

Running both as we refactor and add new features will give us confidence we haven’t broken an existing feature

Slide 128

Slide 128 text

Success!

Slide 129

Slide 129 text

Our purpose today was to get you hooked on Behat & PHPSpec and show you how easy it is to get started.

Slide 130

Slide 130 text

Behat and PHPSpec are both powerful tools

Slide 131

Slide 131 text

PHPSpec can be used at a very granular level to ensure your application logic works correctly

Slide 132

Slide 132 text

Advanced Behat & PHPSpec

Slide 133

Slide 133 text

I encourage you to learn more about Behat & phpspec. Here’s a few areas to consider…

Slide 134

Slide 134 text

Parallel Execution

Slide 135

Slide 135 text

A few approaches to running Behat in parallel to improve it’s performance. Start with: shvetsgroup/ParallelRunner

Slide 136

Slide 136 text

Behat - Reusable Actions

Slide 137

Slide 137 text

“I should see”, “I go to” are just steps - you can write your own steps.

Slide 138

Slide 138 text

Mocking & Prophesying

Slide 139

Slide 139 text

Mock objects are simulated objects that mimic the behavior of real objects

Slide 140

Slide 140 text

Helpful to mock very complex objects, or objects that you don’t want to call while testing - i.e., APIs

Slide 141

Slide 141 text

Prophecy is a highly opinionated PHP mocking framework by the Phpspec team

Slide 142

Slide 142 text

Take a look at the sample code on Github - I mocked a Human Resource Management System API

Slide 143

Slide 143 text

Mocking with Prophecy $this->prophet = new \Prophecy\Prophet; $prophecy = $this->prophet->prophesize('App\HrmsApi'); $prophecy->getUser(Argument::type('string'))- >willReturn('name'); $prophecy->decrement('name', Argument::type('integer'))->willReturn(true); $dummyApi = $prophecy->reveal();

Slide 144

Slide 144 text

PhantomJS

Slide 145

Slide 145 text

Can use PhantomJS with Behat to render Javascript, including automated screenshots and screenshot comparison

Slide 146

Slide 146 text

Two Tasks For You

Slide 147

Slide 147 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 148

Slide 148 text

Keep In Touch! • joind.in/15623 • @JoshuaSWarren • JoshuaWarren.com