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. BDDwith Symfony2

  2. I who am everzet senior from-birth PHP developer at

  3. UnitTest Test code automatically TIMELINE

  4. UnitTest TDD Test code automatically Write tests first TIMELINE

  5. UnitTest TDD Test code automatically Write tests first TIMELINE Dan

    North BDD
  6. BDD ♮ evolution of TDD

  7. - Oh, man, i hate evolutions! What’s wrong with TDD?

  8. Test-Driven Development - Oh, man, i hate evolutions! What’s wrong

    with TDD?
  9. 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?
  10. Test-Driven Development software design In reality, we’re talking bout -

    Oh, man, i hate evolutions! What’s wrong with TDD?
  11. Test-Driven Development Behavior © 2003, Dan North

  12. BDDwas introduced as set of conventions over TDD

  13. 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
  14. 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
  15. 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
  16. ASSERTIONS are TEST-oriented too assertEquals($expected, $actual) assertGreaterThan($expected, $actual) assertInstanceOf($class, $actual)

    TESTing
  17. 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
  18. UnitTest TDD Test code automatically Write tests first Spec BDD

    Design first TIMELINE Dan North BDD
  19. Specification-oriented BDD Frameworks

  20. *Spec RSpec by Dave Astels

  21. *Spec RSpec by Dave Astels JSpec by TJ Holowaychuk

  22. *Spec RSpec by Dave Astels JSpec by TJ Holowaychuk Fabulous

    by Alex Rudakov
  23. 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
  24. 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
  25. BDD SCENARIO ORIENTED photo by dsearls

  26. VOCABULARY photo by dsearls photo by Horia Varlan

  27. VOCABULARY photo by dsearls photo by Horia Varlan for testers

  28. VOCABULARY photo by dsearls photo by Horia Varlan for testers

    for analysts
  29. VOCABULARY photo by dsearls photo by Horia Varlan for testers

    for analysts for developers
  30. VOCABULARY photo by dsearls photo by Horia Varlan for testers

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

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

    developers business ELIMINATING some of the AMBIGUITY and MISCOMMUNICATION
  33. COMMUNICATIONS photo by joshfassbind.com

  34. In order to [A] As a [B] I need [C]

    Story:
  35. 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]
  36. 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
  37. 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:
  38. In order to ... As a ... I need ...

    Story:
  39. Given some initial context (the givens), When an event occurs,

    Then ensure some outcomes. In order to ... As a ... I need ... Story:
  40. 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 ...
  41. 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 ...
  42. UnitTest TDD Test code automatically Write tests first Spec BDD

    Design first Scenario BDD Analyse first TIMELINE Dan North BDD
  43. GHERKINDSL photo by isobel.gordon

  44. 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:
  45. 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
  46. 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
  47. ͳΒ͹ 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
  48. Допустим 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
  49. 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
  50. 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
  51. Acceptance criteria should be executable!

  52. None
  53. 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
  54. 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
  55. DEFINITIONS Given I have a bank account STEP

  56. DEFINITIONS Given I have a bank account STEP <?php Given('/^I

    have a bank account$/');
  57. DEFINITIONS STEP <?php Given('/^I have a bank account$/', function() {

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

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

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

  61. RESULT STEP TYPES 1. Pending step that throw new \Behat\Behat\Exception\Pending();

    2. Undefined step that have no definitions (found)
  62. 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
  63. 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();
  64. 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
  65. 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
  66. DEFINITIONS Given I have a bank account STEP <?php $steps->Given('/^I

    have a bank account$/', function() { throw new \Behat\Behat\Exception\Pending(); } );
  67. 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$
  68. 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$
  69. 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$
  70. 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$
  71. 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$
  72. OUTCOME Then I should have 35$ TESTING

  73. 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!'); } } );
  74. 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!'); } } );
  75. 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()); } );
  76. 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()); } );
  77. BehatBundle

  78. USAGE BehatBundle 1. Install: http://symfony2bundles.org/Behat/BehatBundle

  79. 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
  80. 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
  81. BUNDLEDSteps

  82. 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$/
  83. 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$/
  84. BUNDLEDSteps Request Steps Then /^Request method is (.*)$/ Then /^Request

    has cookie "([^"]*)"$/ Then /^Request has not cookie "([^"]*)"$/ Then /^Request cookie "([^"]*)" is "([^"]*)"$/
  85. 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$/
  86. Feature: User logins

  87. In order to have extended abilities As a site user

    I need to be able to login Feature: User logins
  88. 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
  89. 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
  90. 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
  91. 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
  92. 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
  93. 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
  94. 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
  95. 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
  96. 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
  97. 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
  98. 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 |
  99. USAGE 1. Colorize: $ app/console behat:test:bundle Application\\HelloBundle

  100. 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
  101. http://Behat.org One more thing

  102. In-browser testing and other frameworks support

  103. Sahi

  104. Sahi Goutte

  105. Sahi Goutte Selenium

  106. Sahi Goutte Selenium sfBrowser

  107. Sahi Goutte Selenium sfBrowser Symfony2 Client

  108. Sahi Goutte Selenium sfBrowser Symfony2 Client through one clean API

  109. M!"#

  110. <?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')); } }
  111. <?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')); } }
  112. 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 |
  113. 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
  114. http://Behat.org Questions? http://joind.in/2769