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

BDD in Symfony2

BDD in Symfony2

Presentation from sfLive11 about Behavior Driven Development in Symfony2

Konstantin Kudryashov

September 26, 2011
Tweet

More Decks by Konstantin Kudryashov

Other Decks in Programming

Transcript

  1. Test-Driven Development Are we really talking about tests??? But how

    to test something, that not exists yet? - Oh, man, i hate evolutions! What’s wrong with TDD?
  2. Test-Driven Development software design In reality, we’re talking bout -

    Oh, man, i hate evolutions! What’s wrong with TDD?
  3. BDDwas introduced as set of conventions over TDD Test method

    names should be sentences testFindsCustomerById() testFailsForDuplicateCustomers() tests, that class finds customer by ID tests, that class fails for dup customers
  4. BDDwas introduced as set of conventions over TDD Test method

    names should be sentences testFindsCustomerById() testFailsForDuplicateCustomers() Test method names should start with “should” word shouldFindCustomerById() shouldFailForDuplicateCustomers() tests, that class finds customer by ID tests, that class fails for dup customers class should find customer by ID class should fail for dup customers
  5. BDDwas introduced as set of conventions over TDD Test method

    names should be sentences testFindsCustomerById() testFailsForDuplicateCustomers() Test method names should start with “should” word shouldFindCustomerById() shouldFailForDuplicateCustomers() tests, that class finds customer by ID tests, that class fails for dup customers class should find customer by ID class should fail for dup customers TestCase class should be nouns in test method sentences class CustomerTableTest extends \PHPUnitTestCase { /** * @Test */ shouldFindCustomerById() ... } CustomerTable should find customer by ID
  6. ASSERTIONS are TEST-oriented too assertEquals($expected, $actual) assertGreaterThan($expected, $actual) assertInstanceOf($class, $actual)

    $actual should be Equals to $expected $actual should be GreaterThan $expected $actual should be InstanceOf $class TESTing Describing
  7. RSpec # bowling_spec.rb require 'bowling' describe Bowling, "#score" do it

    "returns 0 for all gutter game" do bowling = Bowling.new 20.times { bowling.hit(0) } bowling.score.should == 0 end end
  8. RSpec # bowling_spec.rb require 'bowling' describe Bowling, "#score" do it

    "returns 0 for all gutter game" do bowling = Bowling.new 20.times { bowling.hit(0) } bowling.score.should == 0 end end SPECIFICATION Write class , not UnitTEST
  9. VOCABULARY photo by dsearls photo by Horia Varlan for testers

    for analysts for developers for business
  10. VOCABULARY photo by dsearls photo by Horia Varlan for testers

    for analysts for developers for business 1
  11. VOCABULARY photo by dsearls photo by Horia Varlan testers analysts

    developers business ELIMINATING some of the AMBIGUITY and MISCOMMUNICATION
  12. A the benefit or value of the feature B the

    person (or role) who will benefit C some feature Story: In order to [A] As a [B] I need [C]
  13. Its strength is that it forces you to identify the

    value of delivering a story when you first define it. © Dan North Story: In order to [A] As a [B] I need [C] A the benefit or value of the feature B the person (or role) who will benefit C some feature
  14. A story’s behaviour is simply its acceptance criteria! if the

    system fulfills all the acceptance criteria, it’s behaving correctly; if it doesn’t, it isn’t. Story:
  15. Given some initial context (the givens), When an event occurs,

    Then ensure some outcomes. In order to ... As a ... I need ... Story:
  16. Given some initial context (the givens), When an event occurs,

    Then ensure some outcomes. Given some initial context (the givens), When an event occurs, Then ensure some outcomes. Story: In order to ... As a ... I need ...
  17. Scenario 1: Scenario 2: Story: Given some initial context (the

    givens), When an event occurs, Then ensure some outcomes. Given some initial context (the givens), When an event occurs, Then ensure some outcomes. In order to ... As a ... I need ...
  18. UnitTest TDD Test code automatically Write tests first Spec BDD

    Design first Scenario BDD Analyse first TIMELINE Dan North BDD
  19. Given some initial context (the givens), When an event occurs,

    Then ensure some outcomes. In order to ... As a ... I need ... Given some initial context (the givens), When an event occurs, Then ensure some outcomes. Scenario 1: Scenario 2: Story:
  20. Given some initial context (the givens) When an event occurs

    Then ensure some outcomes In order to ... As a ... I need ... Given some initial context (the givens) When an event occurs Then ensure some outcomes Scenario: 1st scenario title Scenario: 2nd scenario title Feature: Feature description
  21. Etant donné some initial context (the givens) Lorsque an event

    occurs Alors ensure some outcomes In order to ... As a ... I need ... Etant donné some initial context (the givens) Lorsque an event occurs Alors ensure some outcomes Scénario: 1st scenario title Scénario: 2nd scenario title Fonctionnalité: Feature description # language: fr
  22. ͳΒ͹ some initial context (the givens) ͔͠͠ an event occurs

    લఏ ensure some outcomes In order to ... As a ... I need ... ͳΒ͹ some initial context (the givens) ͔͠͠ an event occurs લఏ ensure some outcomes γφϦΦ: 1st scenario title γφϦΦ: 2nd scenario title ϑΟʔνϟ: Feature description # language: ja
  23. Допустим some initial context (the givens) Когда an event occurs

    То ensure some outcomes In order to ... As a ... I need ... Допустим some initial context (the givens) Когда an event occurs То ensure some outcomes Сценарий: 1st scenario title Сценарий: 2nd scenario title Функционал: Feature description # language: ru
  24. Let go and haul some initial context (the givens) Blimey!

    an event occurs Aye ensure some outcomes In order to ... As a ... I need ... Let go and haul some initial context (the givens) Blimey! an event occurs Aye ensure some outcomes Heave to: 1st scenario title Heave to: 2nd scenario title Ahoy matey!: Feature description # language: en-pirate
  25. Let go and haul some initial context (the givens) Blimey!

    an event occurs Aye ensure some outcomes Let go and haul some initial context (the givens) Blimey! an event occurs Aye ensure some outcomes Heave to: Heave to: Ahoy matey!: # language: en-pirate
  26. In order to ... As a ... I need ...

    Scenario: 1st scenario title Scenario: 2nd scenario title Feature: Feature description Given some initial context (the givens) When an event occurs Then ensure some outcomes Given some initial context (the givens) When an event occurs Then ensure some outcomes
  27. 1. feature 2. scenario 3. step ... ... 2. scenario

    3. step ... ... Given some initial context (the givens) When an event occurs Then ensure some outcomes In order to ... As a ... I need ... Given some initial context (the givens) When an event occurs Then ensure some outcomes Scenario: 1st scenario title Scenario: 2nd scenario title Feature: Feature description feature tree
  28. DEFINITIONS STEP <?php Given('/^I have a bank account$/', function() {

    throw new \Behat\Behat\Exception\Pending(); } ); Given I have a bank account
  29. DEFINITIONS STEP <?php $steps->Given('/^I have a bank account$/', function() {

    throw new \Behat\Behat\Exception\Pending(); } ); Given I have a bank account
  30. DEFINITIONS STEP <?php $steps->Given('/^I have a bank account$/', function() {

    throw new \Behat\Behat\Exception\Pending(); } ); ??? Given I have a bank account
  31. RESULT STEP TYPES 1. Pending step that throw new \Behat\Behat\Exception\Pending();

    2. Undefined step that have no definitions (found)
  32. RESULT STEP TYPES 1. Pending step that throw new \Behat\Behat\Exception\Pending();

    2. Undefined step that have no definitions (found) 3. Ambiguous step which match multiple definitions
  33. RESULT STEP TYPES 1. Pending step that throw new \Behat\Behat\Exception\Pending();

    2. Undefined step that have no definitions (found) 3. Ambiguous step which match multiple definitions 4. Failed step that throw \Exception();
  34. RESULT STEP TYPES 1. Pending step that throw new \Behat\Behat\Exception\Pending();

    2. Undefined step that have no definitions (found) 3. Ambiguous step which match multiple definitions 4. Failed step that throw \Exception(); 5. Skipped step that follows pending/undefined/failed
  35. RESULT STEP TYPES 1. Pending step that throw new \Behat\Behat\Exception\Pending();

    2. Undefined step that have no definitions (found) 3. Ambiguous step which match multiple definitions 4. Failed step that throw \Exception(); 5. Skipped step that follows pending/undefined/failed 6. Passed step that doesn’t throw exceptions
  36. DEFINITIONS Given I have a bank account STEP <?php $steps->Given('/^I

    have a bank account$/', function() { throw new \Behat\Behat\Exception\Pending(); } );
  37. DEFINITIONS Given I have a bank account STEP <?php $steps->Given('/^I

    have a bank account$/', function() { throw new \Behat\Behat\Exception\Pending(); } ); When I deposit 35$
  38. DEFINITIONS STEP <?php $steps->When('/^I deposit (\d+)\$$/', function($dollars) { // $dollars

    === 35 } ); Given I have a bank account <?php $steps->Given('/^I have a bank account$/', function() { throw new \Behat\Behat\Exception\Pending(); } ); When I deposit 35$
  39. DEFINITIONS STEP <?php $steps->When('/^I deposit (\d+)\$$/', function($dollars) { // $dollars

    === 35 } ); Given I have a bank account <?php $steps->Given('/^I have a bank account$/', function() { throw new \Behat\Behat\Exception\Pending(); } ); When I deposit 35$
  40. DEFINITIONS STEP <?php $steps->Given('/^I have a bank account$/', function($world) {

    throw new \Behat\Behat\Exception\Pending(); } ); <?php $steps->When('/^I deposit (\d+)\$$/', function($world, $dollars) { // $dollars === 35 } ); Given I have a bank account When I deposit 35$
  41. DEFINITIONS STEP <?php $steps->Given('/^I have a bank account$/', function($world) {

    $world->account = new BankAccount(); } ); <?php $steps->When('/^I deposit (\d+)\$$/', function($world, $dollars) { $world->account->deposit($dollars); } ); Given I have a bank account When I deposit 35$
  42. OUTCOME Then I should have 35$ TESTING <?php $steps->Then('/^I should

    have (\d+)\$$/', function($world, $balance) { if ($balance !== $world->account->getBalance()) { throw new \Exception('Wrong balance!'); } } );
  43. OUTCOME TESTING Then I should have 35$ ( ) <?php

    $steps->Then('/^I should have (\d+)\$$/', function($world, $balance) { assertEquals($balance, $world->account->getBalance()); } ); using PHPUnit Then I should have 35$ <?php $steps->Then('/^I should have (\d+)\$$/', function($world, $balance) { if ($balance !== $world->account->getBalance()) { throw new \Exception('Wrong balance!'); } } );
  44. DEFINITIONS STEP <?php $steps->Given('/^I have a bank account$/', function($world) {

    $world->account = new BankAccount(); } ); $steps->When('/^I deposit (\d+)\$$/', function($world, $dollars) { $world->account->deposit($dollars); } ); $steps->Then('/^I should have (\d+)\$$/', function($world, $balance) { assertEquals($balance, $world->account->getBalance()); } );
  45. DEFINITIONS STEP <?php $steps-> Given('/^I have a bank account$/', function($world)

    { $world->account = new BankAccount(); } )-> When('/^I deposit (\d+)\$$/', function($world, $dollars) { $world->account->deposit($dollars); } )-> Then('/^I should have (\d+)\$$/', function($world, $balance) { assertEquals($balance, $world->account->getBalance()); } );
  46. USAGE BehatBundle 1. Install: 2. Setup: $ app/console behat:test:bundle --init

    Application\\HelloBundle . ᵋᴷᴷ src/Application/HelloBundle/Tests/Features ᵓᴷᴷ steps ᴹ ᵋᴷᴷ steps.php ᵋᴷᴷ support ᵓᴷᴷ bootstrap.php ᵋᴷᴷ env.php write and put your features here place step definition files here example step definition file place support scripts here bootstrap environment (context) initialization http://symfony2bundles.org/Behat/BehatBundle
  47. USAGE BehatBundle 1. Install: 2. Setup: $ app/console behat:test:bundle --init

    Application\\HelloBundle . ᵋᴷᴷ src/Application/HelloBundle/Tests/Features ᵓᴷᴷ steps ᴹ ᵋᴷᴷ steps.php ᵋᴷᴷ support ᵓᴷᴷ bootstrap.php ᵋᴷᴷ env.php write and put your features here place step definition files here example step definition file place support scripts here bootstrap environment (context) initialization 3. Colorize: $ app/console behat:test:bundle Application\\HelloBundle http://symfony2bundles.org/Behat/BehatBundle
  48. BUNDLEDSteps Browser Steps Given /^I am on(?: the)? (.*)$/ When

    /^I go to(?: the)? (.*)$/ When /^I (?:follow|click)(?: the)? "([^"]*)"(?: link)*$/ When /^I go back$/ When /^I go forward$/ When /^I send (POST|PUT|DELETE) to (.*) with:$/ When /^I follow redirect$/
  49. BUNDLEDSteps Browser Steps Given /^I am on(?: the)? (.*)$/ When

    /^I go to(?: the)? (.*)$/ When /^I (?:follow|click)(?: the)? "([^"]*)"(?: link)*$/ When /^I go back$/ When /^I go forward$/ When /^I send (POST|PUT|DELETE) to (.*) with:$/ When /^I follow redirect$/ Form Steps When /^I fill in "([^"]*)" with "([^"]*)"$/ When /^I select "([^"]*)" from "([^"]*)"$/ When /^I uncheck "([^"]*)"$/ When /^I uncheck "([^"]*)"$/ When /^I attach the file at "([^"]*)" to "([^"]*)"$/ When /^I press "([^"]*)" in (.*) form$/
  50. BUNDLEDSteps Request Steps Then /^Request method is (.*)$/ Then /^Request

    has cookie "([^"]*)"$/ Then /^Request has not cookie "([^"]*)"$/ Then /^Request cookie "([^"]*)" is "([^"]*)"$/
  51. BUNDLEDSteps Request Steps Then /^Request method is (.*)$/ Then /^Request

    has cookie "([^"]*)"$/ Then /^Request has not cookie "([^"]*)"$/ Then /^Request cookie "([^"]*)" is "([^"]*)"$/ Response Steps Then /^Response status code is (\d+)$/ Then /^I should see "([^"]*)"$/ Then /^I should not see "([^"]*)"$/ Then /^I should see element "([^"]*)"$/ Then /^Header "([^"]*)" is set to "([^"]*)"$/ Then /^Header "([^"]*)" is not set to "([^"]*)"$/ Then /^I was redirected$/ Then /^I was not redirected$/ Then /^Print output$/
  52. In order to have extended abilities As a site user

    I need to be able to login Feature: User logins
  53. In order to have extended abilities As a site user

    I need to be able to login Scenario: Existing user can login Feature: User logins
  54. In order to have extended abilities As a site user

    I need to be able to login Scenario: Existing user can login Scenario: Non-existing user can’t login Feature: User logins
  55. Given a site have “everzet” user with “qwerty” password And

    I am on the “/login” When I fill in “username” with “everzet” And I fill in “password” with “qwerty” And I press “login” in login form Then I should see “Welcome, everzet” In order to have extended abilities As a site user I need to be able to login Scenario: Existing user can login Given a site have “everzet” user with “qwerty” password And I am on the “/login” When I fill in “username” with “someone” And I fill in “password” with “qwerty” And I press “login” in login form Then I should see “Login or password is incorrect” Scenario: Non-existing user can’t login Feature: User logins
  56. Given a site have “everzet” user with “qwerty” password And

    I am on the “/login” When I fill in “username” with “everzet” And I fill in “password” with “qwerty” And I press “login” in login form Then I should see “Welcome, everzet” In order to have extended abilities As a site user I need to be able to login Scenario: Existing user can login Given a site have “everzet” user with “qwerty” password And I am on the “/login” When I fill in “username” with “someone” And I fill in “password” with “qwerty” And I press “login” in login form Then I should see “Login or password is incorrect” Scenario: Non-existing user can’t login Feature: User logins
  57. Given I am on the “/login” page When I fill

    in “username” with “everzet” And I fill in “password” with “qwerty” And I press “login” in login form Then I should see “Welcome, everzet” In order to have extended abilities As a site user I need to be able to login Scenario: Existing user can login Given I am on the “/login” page When I fill in “username” with “someone” And I fill in “password” with “qwerty” And I press “login” in login form Then I should see “Login or password is incorrect” Scenario: Non-existing user can’t login Feature: User logins
  58. Given I am on the “/login” page When I fill

    in “username” with “everzet” And I fill in “password” with “qwerty” And I press “login” in login form Then I should see “Welcome, everzet” In order to have extended abilities As a site user I need to be able to login Scenario: Existing user can login Given a site have “everzet” user with “qwerty” password Background: Given I am on the “/login” page When I fill in “username” with “someone” And I fill in “password” with “qwerty” And I press “login” in login form Then I should see “Login or password is incorrect” Scenario: Non-existing user can’t login Feature: User logins
  59. Given I am on the “/login” page When I fill

    in “username” with “everzet” And I fill in “password” with “qwerty” And I press “login” in login form Then I should see “Welcome, everzet” In order to have extended abilities As a site user I need to be able to login Scenario: Existing user can login Given a site have users: Background: Given I am on the “/login” page When I fill in “username” with “someone” And I fill in “password” with “qwerty” And I press “login” in login form Then I should see “Login or password is incorrect” Scenario: Non-existing user can’t login | username | password | | everzet | qwerty | Feature: User logins
  60. Given I am on the “/login” page When I fill

    in “username” with “everzet” And I fill in “password” with “qwerty” And I press “login” in login form Then I should see “Welcome, everzet” In order to have extended abilities As a site user I need to be able to login Scenario: Existing user can login Given a site have users: Background: Given I am on the “/login” page When I fill in “username” with “someone” And I fill in “password” with “qwerty” And I press “login” in login form Then I should see “Login or password is incorrect” Scenario: Non-existing user can’t login | username | password | | everzet | qwerty | Feature: User logins
  61. Given I am on the “/login” page When I fill

    in “username” with “<username>” And I fill in “password” with “<password>” And I press “login” in login form Then I should see “<message>” In order to have extended abilities As a site user I need to be able to login Scenario Outline: Only existing users can login Given a site have users: Background: | username | password | | everzet | qwerty | Feature: User logins
  62. Given I am on the “/login” page When I fill

    in “username” with “<username>” And I fill in “password” with “<password>” And I press “login” in login form Then I should see “<message>” In order to have extended abilities As a site user I need to be able to login Scenario Outline: Only existing users can login Given a site have users: Background: | username | password | | everzet | qwerty | Examples: Feature: User logins
  63. Given I am on the “/login” page When I fill

    in “username” with “<username>” And I fill in “password” with “<password>” And I press “login” in login form Then I should see “<message>” In order to have extended abilities As a site user I need to be able to login Scenario Outline: Only existing users can login Feature: User logins Given a site have users: Background: | username | password | | everzet | qwerty | Examples: | username | password | message | | everzet | qwerty | Welcome, everzet | | someone | pa$$word | Login or password is incorrect |
  64. USAGE 1. Colorize: 2. Write missing steps: <?php $steps->Given('/^a site

    have users:$/', function($world, $table) { $em = $world->getClient()-> getKernel()-> getContainer()-> get('doctrine.orm.entity_manager'); // remove all users from test db with EntityManager foreach ($table->getRowHash() as $row) { // persist new user into test db with EntityManager // $row[‘username’] AND $row[‘password’] } $em->flush(); }); $ app/console behat:test:bundle Application\\HelloBundle
  65. <?php // src/Sensio/HelloBundle/Tests/Controller/HelloControllerTest.php namespace Sensio\HelloBundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Behat\Mink; class

    HelloControllerTest extends WebTestCase { public function testIndexWithSymfony2Client() { $client = $this->createClient(); $driver = new Mink\Driver\Symfony2ClientDriver($client); $session = new Mink\Session($driver); $session->visit('/hello/Fabien'); $page = $session->getPage(); $this->assertTrue($page->hasContent('Hello Fabien')); } public function testIndexWithSahi() { $driver = new Mink\Driver\SahiDriver('SAHI_SESSION_ID'); $session = new Mink\Session($driver); $session->visit('/hello/Fabien'); $page = $session->getPage(); $this->assertTrue($page->hasContent('Hello Fabien')); } }
  66. <?php // src/Sensio/HelloBundle/Tests/Controller/HelloControllerTest.php namespace Sensio\HelloBundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Behat\Mink; class

    HelloControllerTest extends WebTestCase { public function testIndexWithSymfony2Client() { $client = $this->createClient(); $driver = new Mink\Driver\Symfony2ClientDriver($client); $session = new Mink\Session($driver); $session->visit('/hello/Fabien'); $page = $session->getPage(); $this->assertTrue($page->hasContent('Hello Fabien')); } public function testIndexWithSahi() { $driver = new Mink\Driver\SahiDriver('SAHI_SESSION_ID'); $session = new Mink\Session($driver); $session->visit('/hello/Fabien'); $page = $session->getPage(); $this->assertTrue($page->hasContent('Hello Fabien')); } }
  67. Given I am on the “/login” page When I fill

    in “username” with “<username>” And I fill in “password” with “<password>” And I press “login” in login form Then I should see “<message>” In order to have extended abilities As a site user I need to be able to login Scenario Outline: Only existing users can login Feature: User logins Given a site have users: Background: | username | password | | everzet | qwerty | Examples: | username | password | message | | everzet | qwerty | Welcome, everzet | | someone | pa$$word | Login or password is incorrect |
  68. Given I am on the “/login” page When I fill

    in “username” with “<username>” And I fill in “password” with “<password>” And I press “login” in login form Then I should see “<message>” In order to have extended abilities As a site user I need to be able to login Scenario Outline: Only existing users can login Feature: User logins Given a site have users: Background: | username | password | | everzet | qwerty | Examples: | username | password | message | | everzet | qwerty | Welcome, everzet | | someone | pa$$word | Login or password is incorrect | @javascript