Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHPSpec & Behat - Two Testing Tools That Write Code for You - Zendcon 2015 Version

PHPSpec & Behat - Two Testing Tools That Write Code for You - Zendcon 2015 Version

PHPSpec & Behat are two amazing PHP tools that empower specification-driven development and behavior-driven development. These two tools combined can help you build test coverage, but many people don’t realize they can also write much of your code for you. In this talk, we’ll see what PHPSpec & Behat can do with a series of examples and use cases. In other words - I heard you like to code, so I wrote code that writes code while you code.

Zendcon 2015 Version

Joshua Warren

October 22, 2015
Tweet

More Decks by Joshua Warren

Other Decks in Programming

Transcript

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

    View Slide

  2. OR:

    View Slide

  3. View Slide

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

    View Slide

  5. About Me

    View Slide

  6. PHP
    Developer
    Working with PHP since
    1999

    View Slide

  7. Founder &
    CEO
    Founded Creatuity in 2008
    PHP Development Firm
    Focused on the Magento
    platform

    View Slide

  8. JoshuaWarren.com
    @JoshuaSWarren

    View Slide

  9. IMPORTANT!
    • joind.in/15623
    • Download slides
    • Post comments
    • Leave a rating!

    View Slide

  10. What You Need To Know
    ASSUMPTIONS

    View Slide

  11. Today we assume you’re a PHP
    developer.

    View Slide

  12. That you are familiar with test driven
    development.

    View Slide

  13. And that you’ve at least tried PHPUnit,
    Selenium or another testing tool.

    View Slide

  14. BDD - no, the B does not stand for beer, despite what a Brit might
    tell you
    Behavior Driven
    Development

    View Slide

  15. Think of BDD as stepping up a level
    from TDD.

    View Slide

  16. Graphic thanks to BugHuntres

    View Slide

  17. TDD generally deals with functional
    units.

    View Slide

  18. BDD steps up a level to consider complete
    features.

    View Slide

  19. In BDD, you write feature files in the form of
    user stories that you test against.

    View Slide

  20. BDD uses a ubiquitous language - basically,
    a language that business stakeholders,
    project managers, developers and our
    automated tools can all understand.

    View Slide

  21. 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"


    View Slide

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

    View Slide

  23. Behat

    View Slide

  24. We implement BDD in PHP with a tool
    called Behat

    View Slide

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

    View Slide

  26. behat.org

    View Slide

  27. SpecBDD - aka, Testing Tongue Twisters
    Specification Behavior Driven
    Development

    View Slide

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

    View Slide

  29. Focuses you on architectural decisions up-
    front

    View Slide

  30. PHPSpec

    View Slide

  31. Open Source tool for specification driven
    development in PHP

    View Slide

  32. www.phpspec.net

    View Slide

  33. Why Use Behat and
    PHPSpec?

    View Slide

  34. These tools allow you to focus
    exclusively on logic

    View Slide

  35. Helps build functional testing coverage
    quickly

    View Slide

  36. Guides planning and ensuring that all
    stakeholders are in agreement

    View Slide

  37. Why Not PHPUnit?

    View Slide

  38. PHPSpec is opinionated - in every sense of the
    word

    View Slide

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

    View Slide

  40. PHPSpec tests are much more readable

    View Slide

  41. Read any of Marcello Duarte’s slides on testing

    View Slide

  42. What About
    Performance?

    View Slide

  43. Tests that take days to run won’t be used

    View Slide

  44. PHPSpec is fast

    View Slide

  45. Behat supports parallel execution

    View Slide

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

    View Slide

  47. Enough Theory:

    Let’s Build Something!

    View Slide

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

    View Slide

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

    View Slide

  50. Time off requests can be viewed,
    approved and denied.

    View Slide

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

    View Slide

  52. Want to follow along or view the
    sample code?

    View Slide

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

    View Slide

  54. Setting up Our Project

    View Slide

  55. Setup a folder for your project

    View Slide

  56. Use composer to install Behat, phpspec &
    friends

    View Slide

  57. composer require behat/behat —dev

    View Slide

  58. composer require behat/mink-goutte-driver
    —dev

    View Slide

  59. composer require phpspec/phpspec —dev

    View Slide

  60. We now have Behat and Phpspec installed

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  66. Run:
    vendor/bin/behat —init

    View Slide

  67. Create /behat.yml
    default:

    extensions:

    Behat\MinkExtension:

    base_url: http://192.168.33.10/

    default_session: goutte

    goutte: ~


    View Slide

  68. 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

    {


    }

    View Slide

  69. Create /phpspec.yml
    suites:

    app_suites:

    namespace: App

    psr4_prefix: App

    src_path: app


    View Slide

  70. Features

    View Slide

  71. 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"


    View Slide

  72. Run:
    bin/behat

    View Slide

  73. 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"


    View Slide

  74. 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"


    View Slide

  75. 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"


    View Slide

  76. 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"


    View Slide

  77. 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"

    View Slide

  78. 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

    View Slide

  79. 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"

    View Slide

  80. 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"

    View Slide

  81. run behat: bin/behat

    View Slide

  82. 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)

    View Slide

  83. 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.

    View Slide

  84. View Slide

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

    View Slide

  86. Specifications

    View Slide

  87. Now we write specifications for how
    our application should work.

    View Slide

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

    View Slide

  89. bin/phpspec describe App\\Timeoff

    View Slide

  90. PHPSpec generates a basic spec file
    for us

    View Slide

  91. spec\TimeoffSpec.php
    namespace spec\App;


    use PhpSpec\ObjectBehavior;

    use Prophecy\Argument;


    class TimeoffSpec extends ObjectBehavior

    {

    function it_is_initializable()

    {

    $this->shouldHaveType('App\Timeoff');

    }

    }


    View Slide

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

    View Slide

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

    View Slide

  94. 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);

    }

    View Slide

  95. spec\TimeoffSpec.php
    function it_creates_timeoff_requests() {

    $this->create("Name", "reason")-
    >shouldBeString();

    }


    function it_loads_all_timeoff_requests() {

    $this->loadAll()->shouldBeArray();

    }

    View Slide

  96. spec\TimeoffSpec.php
    function it_loads_a_timeoff_request() {

    $this->load("uuid")->shouldBeArray();

    }


    function it_loads_pending_timeoff_requests() {

    $this->loadPending()->shouldBeArray();

    }


    View Slide

  97. spec\TimeoffSpec.php
    function it_approves_timeoff_requests() {

    $this->approve("id")->shouldReturn(true);

    }


    function it_denies_timeoff_requests() {

    $this->deny("id")->shouldReturn(true);

    }

    View Slide

  98. Now we run PHPSpec once more…

    View Slide

  99. 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.

    View Slide

  100. Lots of failures…

    View Slide

  101. But wait a second - PHPSpec prompts
    us!

    View Slide

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

    View Slide

  103. PHPSpec will create the class and the
    methods for us!

    View Slide

  104. This is very powerful with frameworks like
    Laravel and Magento, which have PHPSpec
    plugins that help PHPSpec know where class
    files should be located.

    View Slide

  105. And now, the easy part…

    View Slide

  106. Implementation

    View Slide

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

    View Slide

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

    View Slide

  109. phpspec should be returning all green

    View Slide

  110. Move on to implementing the front-
    end behavior

    View Slide

  111. Using Lumen means our view/display
    logic is very simple

    View Slide

  112. app\Http\route.php
    $app->get('/bdd/', function() use ($app) {

    return "Welcome to the world of BDD";

    });

    View Slide

  113. 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');

    }

    });

    View Slide

  114. 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]);

    }

    View Slide

  115. 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";

    }

    }

    View Slide

  116. app\Http\route.php
    …

    } else {

    $request = $to->load($uuid);

    return
    view('request.manageSpecific', ['request' =>
    $request]);

    }

    View Slide

  117. app\Http\route.php
    …

    } else {

    $requests = $to->loadAll();

    return view('request.manage',
    ['requests' => $requests]);

    }

    View Slide

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

    View Slide

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

    View Slide

  120. Testing

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  128. Success!

    View Slide

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

    View Slide

  130. Behat and PHPSpec are both powerful
    tools

    View Slide

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

    View Slide

  132. Advanced Behat & PHPSpec

    View Slide

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

    View Slide

  134. Parallel Execution

    View Slide

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

    View Slide

  136. Behat - Reusable
    Actions

    View Slide

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

    View Slide

  138. Mocking &
    Prophesying

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  143. 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();

    View Slide

  144. PhantomJS

    View Slide

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

    View Slide

  146. Two Tasks For You

    View Slide

  147. Next week, setup Behat and PHPSpec
    on one of your projects and take it for a
    quick test by implementing one short
    feature.

    View Slide

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

    View Slide