Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

2,730 views

Published on

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

Published in: Technology

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

  1. 1. PHPSpec & Behat: Two Testing Tools That Write Code For You Presented by Joshua Warren
  2. 2. OR:
  3. 3. I heard you like to code, so let’s write code that writes code while you code.
  4. 4. About Me
  5. 5. PHP Developer Working with PHP since 1999
  6. 6. Founder & CEO Founded Creatuity in 2008 PHP Development Firm Focused on the Magento platform Tink, a Creatuity shareholder
  7. 7. JoshuaWarren.com @JoshuaSWarren
  8. 8. IMPORTANT! • joind.in/13744 • Download slides • Post comments • Leave a rating!
  9. 9. What You Need To Know ASSUMPTIONS
  10. 10. Today we assume you’re a PHP developer.
  11. 11. That you are familiar with test driven development.
  12. 12. And that you’ve at least tried PHPUnit, Selenium or another testing tool.
  13. 13. BDD - no, the B does not stand for beer, despite what a Brit might tell you Behavior Driven Development
  14. 14. Think of BDD as stepping up a level from TDD.
  15. 15. Graphic thanks to BugHuntress
  16. 16. TDD generally deals with functional units.
  17. 17. BDD steps up a level to consider complete features.
  18. 18. In BDD, you write feature files in the form of user stories that you test against.
  19. 19. BDD uses a ubiquitous language - basically, a language that business stakeholders, project managers, developers and our automated tools can all understand.
  20. 20. 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"

  21. 21. BDD gets all stakeholders to agree on what “done” looks like before you write a single line of code
  22. 22. Behat
  23. 23. We implement BDD in PHP with a tool called Behat
  24. 24. Behat is a free, open source tool designed for BDD and PHP
  25. 25. behat.org
  26. 26. SpecBDD - aka, Testing Tongue Twisters Specification Behavior Driven Development
  27. 27. Before you write a line of code, you write a specification for how that code should work
  28. 28. Focuses you on architectural decisions up-front
  29. 29. PHPSpec
  30. 30. Open Source tool for specification driven development in PHP
  31. 31. www.phpspec.net
  32. 32. Why Use Behat and PHPSpec?
  33. 33. These tools allow you to focus exclusively on logic
  34. 34. Helps build functional testing coverage quickly
  35. 35. Guides planning and ensuring that all stakeholders are in agreement
  36. 36. Why Not PHPUnit?
  37. 37. PHPSpec is opinionated - in every sense of the word
  38. 38. PHPSpec forces you to think differently and creates a mindset that encourages usage
  39. 39. PHPSpec tests are much more readable
  40. 40. Read any of Marcello Duarte’s slides on testing
  41. 41. What About Performance?
  42. 42. Tests that take days to run won’t be used
  43. 43. PHPSpec is fast
  44. 44. Behat supports parallel execution
  45. 45. Behat and PHPSpec will be at least as fast as the existing testing tools, and can be much faster
  46. 46. Enough Theory: Let’s Build Something!
  47. 47. We’ll be building a basic time-off request app.
  48. 48. Visitors can specify their name and a reason for their time off request.
  49. 49. Time off requests can be viewed, approved and denied.
  50. 50. Intentionally keeping things simple, but you can follow this pattern to add authentication, roles, etc.
  51. 51. Want to follow along or view the sample code?
  52. 52. Vagrant box: https://github.com/joshuaswarren/bdd-box Project code: https://github.com/joshuaswarren/bdd
  53. 53. Setting up Our Project
  54. 54. Setup a folder for your project
  55. 55. Use composer to install Behat, phpspec & friends
  56. 56. composer require behat/behat —dev
  57. 57. composer require behat/mink-goutte-driver —dev
  58. 58. composer require phpspec/phpspec —dev
  59. 59. We now have Behat and Phpspec installed
  60. 60. We also have Mink - an open source browser emulator/controller
  61. 61. Mink Drivers Goutte - headless, fast, no JS Selenium2 - requires Selenium server, slower, supports JS Zombie - headless, fast, does support JS
  62. 62. We are using Goutte today because we don’t need Javascript support
  63. 63. We’ll perform some basic configuration to let Behat know to use Goutte
  64. 64. And we need to let phpspec know where our code should go
  65. 65. Run: vendor/bin/behat —init
  66. 66. Create /behat.yml default:
 extensions:
 BehatMinkExtension:
 base_url: http://192.168.33.10/
 default_session: goutte
 goutte: ~

  67. 67. features/bootstrap/FeatureContext.php use BehatBehatContextContext;
 use BehatBehatContextSnippetAcceptingContext;
 use BehatGherkinNodePyStringNode;
 use BehatGherkinNodeTableNode;
 use BehatMinkExtensionContextMinkContext;
 
 /**
 * Defines application features from the specific context.
 */
 class FeatureContext extends BehatMinkExtensionContextMinkContext
 {
 
 }
  68. 68. Create /phpspec.yml suites:
 app_suites:
 namespace: App
 psr4_prefix: App
 src_path: app

  69. 69. Features
  70. 70. 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"

  71. 71. Run: bin/behat
  72. 72. 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"

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

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

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

  76. 76. 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"
  77. 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
  78. 78. 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"
  79. 79. 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"
  80. 80. run behat: bin/behat
  81. 81. 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)
  82. 82. 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.
  83. 83. These failures show us that Behat is testing our app properly, and now we just need to write the application logic.
  84. 84. Specifications
  85. 85. Now we write specifications for how our application should work.
  86. 86. These specifications should provide the logic to deliver the results that Behat is testing for.
  87. 87. bin/phpspec describe AppTimeoff
  88. 88. PHPSpec generates a basic spec file for us
  89. 89. specTimeoffSpec.php namespace specApp;
 
 use PhpSpecObjectBehavior;
 use ProphecyArgument;
 
 class TimeoffSpec extends ObjectBehavior
 {
 function it_is_initializable()
 {
 $this->shouldHaveType('AppTimeoff');
 }
 }

  90. 90. This default spec tells PHPSpec to expect a class named Timeoff.
  91. 91. Now we add a bit more to the file so PHPSpec will understand what this class should do.
  92. 92. specTimeoffSpec.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);
 }
  93. 93. specTimeoffSpec.php function it_creates_timeoff_requests() {
 $this->create("Name", "reason")->shouldBeString();
 }
 
 function it_loads_all_timeoff_requests() {
 $this->loadAll()->shouldBeArray();
 }
  94. 94. specTimeoffSpec.php function it_loads_a_timeoff_request() {
 $this->load("uuid")->shouldBeArray();
 }
 
 function it_loads_pending_timeoff_requests() {
 $this->loadPending()->shouldBeArray();
 }

  95. 95. specTimeoffSpec.php function it_approves_timeoff_requests() {
 $this->approve("id")->shouldReturn(true);
 }
 
 function it_denies_timeoff_requests() {
 $this->deny("id")->shouldReturn(true);
 }
  96. 96. Now we run PHPSpec once more…
  97. 97. Phpspec output 10 ✔ is initializable 15 ! creates timeoff requests method AppTimeoff::create not found. 19 ! loads all timeoff requests method AppTimeoff::loadAll not found. 23 ! loads pending timeoff requests method AppTimeoff::loadPending not found. 27 ! approves timeoff requests method AppTimeoff::approve not found. 31 ! denies timeoff requests method AppTimeoff::deny not found.
  98. 98. Lots of failures…
  99. 99. But wait a second - PHPSpec prompts us!
  100. 100. PHPSpec output Do you want me to create `AppTimeoff::create()` for you? [Y/n]
  101. 101. PHPSpec will create the class and the methods for us!
  102. 102. This is very powerful with frameworks like Laravel and Magento, which have PHPSpec plugins that help PHPSpec know where class files should be located.
  103. 103. And now, the easy part…
  104. 104. Implementation
  105. 105. Implement logic in the new Timeoff class in the locations directed by PHPSpec
  106. 106. Implement each function one at a time, running phpspec after each one.
  107. 107. specTimeoffSpec.php public function create($name, $reason)
 {
 $uuid1 = Uuid::uuid1();
 $uuid = $uuid1->toString();
 DB::table('requests')->insert([
 'name' => $name,
 'reason' => $reason,
 'uuid' => $uuid,
 ]);
 return $uuid;
 }
  108. 108. specTimeoffSpec.php public function load($uuid) {
 $results = DB::select('select * from requests WHERE uuid = ?', [$uuid]);
 return $results;
 }
  109. 109. specTimeoffSpec.php public function loadAll()
 {
 $results = DB::select('select * from requests');
 return $results;
 }
  110. 110. specTimeoffSpec.php public function loadPending()
 {
 $results = DB::select('select * from requests WHERE reviewed = ?', [0]);
 return $results;
 }
  111. 111. specTimeoffSpec.php public function approve($uuid)
 {
 DB::update('update requests set reviewed = 1, approved = 1 where uuid = ?', [$uuid]);
 return true;
 }
  112. 112. specTimeoffSpec.php public function deny($uuid)
 {
 DB::update('update requests set reviewed = 1, approved = 0 where uuid = ?', [$uuid]);
 return true;
 }
  113. 113. phpspec should be returning all green
  114. 114. Move on to implementing the front-end behavior
  115. 115. Using Lumen means our view/display logic is very simple
  116. 116. appHttproute.php $app->get('/bdd/', function() use ($app) {
 return "Welcome to the world of BDD";
 });
  117. 117. appHttproute.php $app->get('/bdd/timeoff/new/', function() use ($app) {
 if(Request::has('name')) {
 $to = new AppTimeoff();
 $name = Request::input('name');
 $reason = Request::input('reason');
 $to->create($name, $reason);
 return "Time off request submitted";
 } else {
 return view('request.new');
 }
 });
  118. 118. appHttproute.php $app->get('/bdd/timeoff/manage/', function() use ($app) {
 $to = new AppTimeoff();
 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]);
 }
  119. 119. appHttproute.php $app->get('/bdd/timeoff/manage/', function() use ($app) {
 $to = new AppTimeoff();
 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";
 }
 } …
  120. 120. appHttproute.php …
 } else {
 $request = $to->load($uuid);
 return view('request.manageSpecific', ['request' => $request]);
 } …
  121. 121. appHttproute.php …
 } else {
 $requests = $to->loadAll();
 return view('request.manage', ['requests' => $requests]);
 }
  122. 122. Our views are located in resourcesviewsrequest and are simple HTML forms
  123. 123. Once we’re done with the implementation, we move on to…
  124. 124. Testing
  125. 125. Once we’re done, running phpspec run should return green
  126. 126. Once phpspec returns green, run behat, which should return green as well
  127. 127. We now know that our new feature is working correctly without needing to open a web browser
  128. 128. PHPSpec gives us confidence that the application logic was implemented correctly.
  129. 129. Behat gives us confidence that the feature is being displayed properly to users.
  130. 130. Running both as we refactor and add new features will give us confidence we haven’t broken an existing feature
  131. 131. Success!
  132. 132. Our purpose today was to get you hooked on Behat & PHPSpec and show you how easy it is to get started.
  133. 133. Behat and PHPSpec are both powerful tools
  134. 134. PHPSpec can be used at a very granular level to ensure your application logic works correctly
  135. 135. Advanced Behat & PHPSpec
  136. 136. I encourage you to learn more about Behat & phpspec. Here’s a few areas to consider…
  137. 137. Parallel Execution
  138. 138. A few approaches to running Behat in parallel to improve it’s performance. Start with: shvetsgroup/ParallelRunner
  139. 139. Behat - Reusable Actions
  140. 140. “I should see”, “I go to” are just steps - you can write your own steps.
  141. 141. Mocking & Prophesying
  142. 142. Mock objects are simulated objects that mimic the behavior of real objects
  143. 143. Helpful to mock very complex objects, or objects that you don’t want to call while testing - i.e., APIs
  144. 144. Prophecy is a highly opinionated PHP mocking framework by the Phpspec team
  145. 145. Take a look at the sample code on Github - I mocked a Human Resource Management System API
  146. 146. Mocking with Prophecy $this->prophet = new ProphecyProphet; $prophecy = $this->prophet->prophesize('AppHrmsApi'); $prophecy->getUser(Argument::type('string'))- >willReturn('name'); $prophecy->decrement('name', Argument::type('integer'))- >willReturn(true); $dummyApi = $prophecy->reveal();
  147. 147. PhantomJS
  148. 148. Stick around - Michelle Sanver is up next at 3:30PM in this room to discuss Behat + PhantomJS including automated screenshots and screenshot comparision
  149. 149. Two Tasks For You
  150. 150. Next week, setup Behat and PHPSpec on one of your projects and take it for a quick test by implementing one short feature.
  151. 151. Keep In Touch! • joind.in/13744 • @JoshuaSWarren • JoshuaWarren.com

×