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

Story Telling & Feature Testing - DallasPHP Nov...

Jeff Carouth
November 13, 2012

Story Telling & Feature Testing - DallasPHP November 2012

Every new feature added to your project and bug report filed against it has background and context. Keeping the relevant context in your project is important for future developers (including yourself) but it's not always easy to include without lengthy comment blocks. Tests provide a mechanism to write the context with code, but most non-developers will not understand what you have written without plain ol' words to explain it. Stories bridge the gap between developers and non-developers, and using a testing framework called Behat, you can become an expert storyteller while drastically improving the amount of relevant information in your project and the quality and quantity of automated, repeatable tests to prove your code behaves as intended. At the end of this session you will have gained an understanding of the components of a good user story, how to adapt the story to steps for use with the Gherkin DSL, and how to automatically run these story tests as you drive development of a feature using Behat.

Jeff Carouth

November 13, 2012
Tweet

More Decks by Jeff Carouth

Other Decks in Programming

Transcript

  1. A potential speaker should be able to submit proposals to

    the CfP As a <who> I want <what> So that <why>
  2. A potential speaker should be able to submit proposals to

    the CfP As a potential speaker I want to submit proposals So that my talks can be considered for a conference
  3. A potential speaker should be able to submit proposals to

    the CfP As a potential speaker I want to submit a proposal So that my talk can be considered for a conference For <benefit> as <type of user> I need <functionality>
  4. The speaker has access to a proposal submission form. The

    speaker cannot submit the proposal form without completing all required fields.
  5. The speaker has access to a proposal submission form. The

    speaker cannot submit the proposal form without completing all required fields. The speaker should see a confirmation message that his or her proposal was received.
  6. The Gherkin Language Given some background context When an event

    occurs Then some verification criteria is true
  7. Given I am on the homepage When I click ‘Submit

    a Proposal’ And I fill in ‘Email’ with ‘[email protected]’ And I click ‘Submit’ Then I should see ‘Please fill in all required fields’
  8. Feature: ls In order to see the directory structure As

    a UNIX user I need to be able to list the current directory's contents Scenario: List 2 files in a directory Given I am in a directory "test" And I have a file named "foo" And I have a file named "bar" When I run "ls" Then I should get: """ bar foo """
  9. Feature: Submit to CfP As a potential speaker I need

    to be able to submit to the CfP So that my proposals can be considered for a conference Scenario: I should have access to the submission form Given I am on homepage When I follow "Submit a Proposal" Then I should see "Please fill in the form below" Scenario: I cannot submit blank required fields Given I am on "/submit" When I press "Submit" Then I should see "Please fill in all required fields" Scenario: I should see a confirmation Given I am on "/submit" When I fill in "Email" with "[email protected]" And I fill in "Title" with "Super Awesome Talk" And I press "Submit" Then I should see "Thank you for submitting to the CfP"
  10. { "name": "jcarouth/cfp", "require": { "php": ">=5.3.8", "silex/silex": "1.0.*", "twig/twig":

    ">=1.8.0,<2.0-dev", "symfony/security": "2.1.*", "symfony/translation": "2.1.*", "symfony/twig-bridge": "2.1.*", "symfony/validator": "2.1.*" }, "require-dev": { "behat/behat": "2.4.*@stable", "behat/mink": "1.4.*@stable", "behat/mink-extension": "*", "behat/mink-goutte-driver": "*", "behat/mink-selenium2-driver": "*", "phpunit/phpunit": "3.7.*", "ext-xdebug": ">=2.2.1" }, "minimum-stability": "dev", "config": { "bin-dir": "bin/" } }
  11. { "name": "jcarouth/cfp", "require": { "php": ">=5.3.8", "silex/silex": "1.0.*", "twig/twig":

    ">=1.8.0,<2.0-dev", "symfony/security": "2.1.*", "symfony/translation": "2.1.*", "symfony/twig-bridge": "2.1.*", "symfony/validator": "2.1.*" }, "require-dev": { "behat/behat": "2.4.*@stable", "behat/mink": "1.4.*@stable", "behat/mink-extension": "*", "behat/mink-goutte-driver": "*", "behat/mink-selenium2-driver": "*", "phpunit/phpunit": "3.7.*", "ext-xdebug": ">=2.2.1" }, "minimum-stability": "dev", "config": { "bin-dir": "bin/" } } "require-dev": { "behat/behat": "2.4.*@stable", "behat/mink": "1.4.*@stable", "behat/mink-extension": "*", "behat/mink-goutte-driver": "*", "behat/mink-selenium2-driver": "*", "phpunit/phpunit": "3.7.*", "ext-xdebug": ">=2.2.1" },
  12. $ curl -s https://getcomposer.org/installer | php $ php bin/composer.phar install

    --dev Loading composer repositories with package information Installing dependencies ...snip... Loading composer repositories with package information Installing dev dependencies ...snip... - Installing behat/mink (v1.4.0) Downloading: 100% ...snip... - Installing behat/gherkin (v2.2.5) Downloading: 100% - Installing behat/behat (v2.4.4) Downloading: 100% - Installing behat/mink-extension (dev-master 71fed8e) Cloning 71fed8e03d403dbeedf74c8764f5d137f7dc9ffd ...snip... Writing lock file Generating autoload files
  13. $ bin/behat --init +d features - place your *.feature files

    here +d features/bootstrap - place bootstrap scripts and static files here +f features/bootstrap/FeatureContext.php - feature related code here
  14. Feature: Submit to CfP As a potential speaker I need

    to be able to submit to the CfP So that my proposals can be considered for a conference Scenario: I should have access to the submission form Given I am on homepage When I follow "Submit a Proposal" Then I should see "Please fill in the form below" Scenario: I cannot submit blank required fields Given I am on "/submit" When I press "Submit" Then I should see "Please fill in all required fields" Scenario: I should see a confirmation Given I am on "/submit" When I fill in "Email" with "[email protected]" And I fill in "Title" with "Super Awesome Talk" And I press "Submit" Then I should see "Thank you for submitting to the CfP" Scenario: I cannot submit blank required fields Given I am on "/submit" When I press "Submit" Then I should see "Please fill in all required fields"
  15. $ bin/behat --init +d features - place your *.feature files

    here +d features/bootstrap - place bootstrap scripts and static files here +f features/bootstrap/FeatureContext.php - feature related code here $ bin/behat -dl $ bin/behat -dl
  16. /** * Features context. */ class FeatureContext extends BehatContext {

    /* ...snip... */ /** * Opens specified page. * * @Given /^(?:|I )am on "(?P<page>[^"]+)"$/ */ public function visit($page) { $this->getSession()->visit($this->locatePath($page)); } }
  17. /** * Features context. */ class FeatureContext extends BehatContext {

    /* ...snip... */ /** * Opens specified page. * * @Given /^(?:|I )am on "(?P<page>[^"]+)"$/ */ public function visit($page) { $this->getSession()->visit($this->locatePath($page)); } } @Given /^(?:|I )am on "(?P<page>[^"]+)"$/ public function visit($page)
  18. <?php use Behat\Behat\Context\ClosuredContextInterface, Behat\Behat\Context\TranslatedContextInterface, Behat\Behat\Context\BehatContext, Behat\Behat\Exception\PendingException; use Behat\Gherkin\Node\PyStringNode, Behat\Gherkin\Node\TableNode; use

    Behat\MinkExtension\Context\MinkContext; /** * Features context. */ class FeatureContext extends MinkContext { /* ...snip boilerplate... */ } use Behat\MinkExtension\Context\MinkContext; extends MinkContext
  19. $ bin/behat -dl Given /^(?:|I )am on homepage$/ When /^(?:|I

    )go to homepage$/ Given /^(?:|I )am on "(?P<page>[^"]+)"$/ When /^(?:|I )go to "(?P<page>[^"]+)"$/ When /^(?:|I )reload the page$/ When /^(?:|I )move backward one page$/ When /^(?:|I )move forward one page$/ When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/ When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/ When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/ When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)"$/ When /^(?:|I )fill in the following:$/ When /^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/ When /^(?:|I )additionally select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/ When /^(?:|I )check "(?P<option>(?:[^"]|\\")*)"$/ When /^(?:|I )uncheck "(?P<option>(?:[^"]|\\")*)"$/ When /^(?:|I )attach the file "(?P[^"]*)" to "(?P<field>(?:[^"]|\\")*)"$/ Then /^(?:|I )should be on "(?P<page>[^"]+)"$/ Then /^the (?i)url(?-i) should match (?P<pattern>"([^"]|\\")*")$/ Then /^the response status code should be (?P<code>\d+)$/ Then /^the response status code should not be (?P<code>\d+)$/ Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/ Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)"$/ Then /^(?:|I )should see text matching (?P<pattern>"(?:[^"]|\\")*")$/ Then /^(?:|I )should not see text matching (?P<pattern>"(?:[^"]|\\")*")$/ Then /^the response should contain "(?P<text>(?:[^"]|\\")*)"$/ Then /^the response should not contain "(?P<text>(?:[^"]|\\")*)"$/ Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/ Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/ Then /^the "(?P<element>[^"]*)" element should contain "(?P<value>(?:[^"]|\\")*)"$/ Then /^the "(?P<element>[^"]*)" element should not contain "(?P<value>(?:[^"]|\\")*)"$/ Then /^(?:|I )should see an? "(?P<element>[^"]*)" element$/ Then /^(?:|I )should not see an? "(?P<element>[^"]*)" element$/ Then /^the "(?P<field>(?:[^"]|\\")*)" field should contain "(?P<value>(?:[^"]|\\")*)"$/ Then /^the "(?P<field>(?:[^"]|\\")*)" field should not contain "(?P<value>(?:[^"]|\\")*)"$/ Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should be checked$/ Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should not be checked$/ Then /^(?:|I )should see (?P<num>\d+) "(?P<element>[^"]*)" elements?$/ Then /^print last response$/ Then /^show last response$/
  20. $ bin/behat -dl Given /^(?:|I )am on homepage$/ When /^(?:|I

    )go to homepage$/ Given /^(?:|I )am on "(?P<page>[^"]+)"$/ When /^(?:|I )go to "(?P<page>[^"]+)"$/ When /^(?:|I )reload the page$/ When /^(?:|I )move backward one page$/ When /^(?:|I )move forward one page$/ When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/ When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/ When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/ When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)"$/ When /^(?:|I )fill in the following:$/ When /^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/ When /^(?:|I )additionally select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/ When /^(?:|I )check "(?P<option>(?:[^"]|\\")*)"$/ When /^(?:|I )uncheck "(?P<option>(?:[^"]|\\")*)"$/ When /^(?:|I )attach the file "(?P[^"]*)" to "(?P<field>(?:[^"]|\\")*)"$/ Then /^(?:|I )should be on "(?P<page>[^"]+)"$/ Then /^the (?i)url(?-i) should match (?P<pattern>"([^"]|\\")*")$/ Then /^the response status code should be (?P<code>\d+)$/ Then /^the response status code should not be (?P<code>\d+)$/ Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/ Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)"$/ Then /^(?:|I )should see text matching (?P<pattern>"(?:[^"]|\\")*")$/ Then /^(?:|I )should not see text matching (?P<pattern>"(?:[^"]|\\")*")$/ Then /^the response should contain "(?P<text>(?:[^"]|\\")*)"$/ Then /^the response should not contain "(?P<text>(?:[^"]|\\")*)"$/ Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/ Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/ Then /^the "(?P<element>[^"]*)" element should contain "(?P<value>(?:[^"]|\\")*)"$/ Then /^the "(?P<element>[^"]*)" element should not contain "(?P<value>(?:[^"]|\\")*)"$/ Then /^(?:|I )should see an? "(?P<element>[^"]*)" element$/ Then /^(?:|I )should not see an? "(?P<element>[^"]*)" element$/ Then /^the "(?P<field>(?:[^"]|\\")*)" field should contain "(?P<value>(?:[^"]|\\")*)"$/ Then /^the "(?P<field>(?:[^"]|\\")*)" field should not contain "(?P<value>(?:[^"]|\\")*)"$/ Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should be checked$/ Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should not be checked$/ Then /^(?:|I )should see (?P<num>\d+) "(?P<element>[^"]*)" elements?$/ Then /^print last response$/ Then /^show last response$/ Scenario: I should see a confirmation Given I am on "/submit" When I fill in "Email" with "[email protected]" And I fill in "Title" with "Super Awesome Talk" And I press "Submit" Then I should see "Thank you for submitting to the CfP"
  21. $ bin/behat -dl Given /^(?:|I )am on homepage$/ When /^(?:|I

    )go to homepage$/ Given /^(?:|I )am on "(?P<page>[^"]+)"$/ When /^(?:|I )go to "(?P<page>[^"]+)"$/ When /^(?:|I )reload the page$/ When /^(?:|I )move backward one page$/ When /^(?:|I )move forward one page$/ When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/ When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/ When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/ When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)"$/ When /^(?:|I )fill in the following:$/ When /^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/ When /^(?:|I )additionally select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/ When /^(?:|I )check "(?P<option>(?:[^"]|\\")*)"$/ When /^(?:|I )uncheck "(?P<option>(?:[^"]|\\")*)"$/ When /^(?:|I )attach the file "(?P[^"]*)" to "(?P<field>(?:[^"]|\\")*)"$/ Then /^(?:|I )should be on "(?P<page>[^"]+)"$/ Then /^the (?i)url(?-i) should match (?P<pattern>"([^"]|\\")*")$/ Then /^the response status code should be (?P<code>\d+)$/ Then /^the response status code should not be (?P<code>\d+)$/ Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/ Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)"$/ Then /^(?:|I )should see text matching (?P<pattern>"(?:[^"]|\\")*")$/ Then /^(?:|I )should not see text matching (?P<pattern>"(?:[^"]|\\")*")$/ Then /^the response should contain "(?P<text>(?:[^"]|\\")*)"$/ Then /^the response should not contain "(?P<text>(?:[^"]|\\")*)"$/ Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/ Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/ Then /^the "(?P<element>[^"]*)" element should contain "(?P<value>(?:[^"]|\\")*)"$/ Then /^the "(?P<element>[^"]*)" element should not contain "(?P<value>(?:[^"]|\\")*)"$/ Then /^(?:|I )should see an? "(?P<element>[^"]*)" element$/ Then /^(?:|I )should not see an? "(?P<element>[^"]*)" element$/ Then /^the "(?P<field>(?:[^"]|\\")*)" field should contain "(?P<value>(?:[^"]|\\")*)"$/ Then /^the "(?P<field>(?:[^"]|\\")*)" field should not contain "(?P<value>(?:[^"]|\\")*)"$/ Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should be checked$/ Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should not be checked$/ Then /^(?:|I )should see (?P<num>\d+) "(?P<element>[^"]*)" elements?$/ Then /^print last response$/ Then /^show last response$/ Scenario: I should see a confirmation Given I am on "/submit" When I fill in "Email" with "[email protected]" And I fill in "Title" with "Super Awesome Talk" And I press "Submit" Then I should see "Thank you for submitting to the CfP" Given /^(?:|I )am on "(?P<page>[^"]+)"$/ When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/ Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/ When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/
  22. Feature: Submit to CfP As a potential speaker I need

    to be able to submit to the CfP So that my proposals can be considered for a conference Scenario: I should have access to the submission form Given I have username "[email protected]" password "asdfghjkl;" And I am on homepage When I follow "Submit a Proposal" Then I should see "Please fill in the form below" Given I have username "[email protected]" password "asdfghjkl;"
  23. $ bin/behat features/submittocfp.feature Feature: Submit to CfP As a potential

    speaker I need to be able to submit to the CfP So that my proposals can be considered for a conference Scenario: I should have access to the submission form Given I have username "[email protected]" password "asdfghjkl;" And I am on homepage When I follow "Submit a Proposal" Then I should see "Please fill in the form below" # ...snip... 3 scenarios (1 undefined, 2 failed) 12 steps (2 passed, 7 skipped, 1 undefined, 2 failed) 0m1.016s # ...snip...
  24. $ bin/behat features/submittocfp.feature Feature: Submit to CfP # ...snip... 3

    scenarios (1 undefined, 2 failed) 12 steps (2 passed, 7 skipped, 1 undefined, 2 failed) 0m1.016s You can implement step definitions for undefined steps with these snippets: /** * @Given /^I have username "([^"]*)" password "([^"]*)"$/ */ public function iHaveAccountUsernamePassword($arg1, $arg2) { throw new PendingException(); }
  25. class FeatureContext extends MinkContext { /** * Initializes context. *

    Every scenario gets it's own context object. * * @param array $parameters context parameters (set them up throug */ public function __construct(array $parameters) { // Initialize your context here } /** * @Given /^I have username "([^"]*)" password "([^"]*)"$/ */ public function iHaveAccount($username, $pass) { $user = new \Jcarouth\User($username); $user->setPassword($pass); $userRepository->save($user); } } /** * @Given /^I have username "([^"]*)" password "([^"]*)"$/ */ public function iHaveAccount($username, $pass) { $user = new \Jcarouth\User($username); $user->setPassword($pass); $userRepository->save($user); } } class FeatureContext extends MinkContext {