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

PHPSpec & Behat: Two Testing Tools That Write C...

PHPSpec & Behat: Two Testing Tools That Write Code For You (#phptek edition)

PHPSpec and 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 and Behat can do, through 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.

Presented at #phptek - http://tek.phparch.com

Joshua Warren

May 21, 2015
Tweet

More Decks by Joshua Warren

Other Decks in Programming

Transcript

  1. OR:

  2. I heard you like to code, so let’s write code

    that writes code while you code.
  3. Founder & CEO Founded Creatuity in 2008 PHP Development Firm

    Focused on the Magento platform Tink, a Creatuity shareholder
  4. BDD - no, the B does not stand for beer,

    despite what a Brit might tell you Behavior Driven Development
  5. In BDD, you write feature files in the form of

    user stories that you test against.
  6. BDD uses a ubiquitous language - basically, a language that

    business stakeholders, project managers, developers and our automated tools can all understand.
  7. 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"

  8. BDD gets all stakeholders to agree on what “done” looks

    like before you write a single line of code
  9. Before you write a line of code, you write a

    specification for how that code should work
  10. Behat and PHPSpec will be at least as fast as

    the existing testing tools, and can be much faster
  11. Mink Drivers Goutte - headless, fast, no JS Selenium2 -

    requires Selenium server, slower, supports JS Zombie - headless, fast, does support JS
  12. 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
 {
 
 }
  13. 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"

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

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

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

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

  18. 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"
  19. 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
  20. 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"
  21. 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"
  22. 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.
  23. These failures show us that Behat is testing our app

    properly, and now we just need to write the application logic.
  24. spec\TimeoffSpec.php namespace spec\App;
 
 use PhpSpec\ObjectBehavior;
 use Prophecy\Argument;
 
 class

    TimeoffSpec extends ObjectBehavior
 {
 function it_is_initializable()
 {
 $this->shouldHaveType('App\Timeoff');
 }
 }

  25. Now we add a bit more to the file so

    PHPSpec will understand what this class should do.
  26. 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);
 }
  27. 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.
  28. This is very powerful with frameworks like Laravel and Magento,

    which have PHPSpec plugins that help PHPSpec know where class files should be located.
  29. spec\TimeoffSpec.php public function create($name, $reason)
 {
 $uuid1 = Uuid::uuid1();
 $uuid

    = $uuid1->toString();
 DB::table('requests')->insert([
 'name' => $name,
 'reason' => $reason,
 'uuid' => $uuid,
 ]);
 return $uuid;
 }
  30. spec\TimeoffSpec.php public function load($uuid) {
 $results = DB::select('select * from

    requests WHERE uuid = ?', [$uuid]);
 return $results;
 }
  31. 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');
 }
 });
  32. 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]);
 }
  33. 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";
 }
 } …
  34. We now know that our new feature is working correctly

    without needing to open a web browser
  35. Running both as we refactor and add new features will

    give us confidence we haven’t broken an existing feature
  36. Our purpose today was to get you hooked on Behat

    & PHPSpec and show you how easy it is to get started.
  37. PHPSpec can be used at a very granular level to

    ensure your application logic works correctly
  38. I encourage you to learn more about Behat & phpspec.

    Here’s a few areas to consider…
  39. A few approaches to running Behat in parallel to improve

    it’s performance. Start with: shvetsgroup/ParallelRunner
  40. Helpful to mock very complex objects, or objects that you

    don’t want to call while testing - i.e., APIs
  41. Take a look at the sample code on Github -

    I mocked a Human Resource Management System API
  42. 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();
  43. Stick around - Michelle Sanver is up next at 3:30PM

    in this room to discuss Behat + PhantomJS including automated screenshots and screenshot comparision
  44. Next week, setup Behat and PHPSpec on one of your

    projects and take it for a quick test by implementing one short feature.