Behat + Mink + PhantomJS = TEST ALL THE THINGS!

Behat + Mink + PhantomJS = TEST ALL THE THINGS!

01da6d807a29ad6d49801c0157518148?s=128

Michelle Sanver

September 23, 2015
Tweet

Transcript

  1. @michellesanver Behat+Mink+PhantomJS = TEST ALL THE THINGS* * Maybe not

    all the things
  2. @michellesanver

  3. @michellesanver WIIIIIIIE \o/ “Learn the most by sharing your knowledge

    with others” - @coderabbi
  4. @michellesanver Let’s talk Come forward

  5. @michellesanver This talk is entry-level.

  6. @michellesanver

  7. @michellesanver

  8. @michellesanver Michelle Sanver

  9. @michellesanver Michelle Sanver Co-President of PHPWomen

  10. @michellesanver PHPWomen 30 dollars

  11. @michellesanver Michelle Sanver Accent(s)!?

  12. @michellesanver Michelle Sanver

  13. @michellesanver I’m a backend developer.

  14. @michellesanver

  15. @michellesanver This talk is Open Source

  16. @michellesanver Audience Do you test your application? (With automatic testing

    not just clicking in a browser)
  17. @michellesanver I would have a picture of grumpy programmer here,

    but I didn’t want to pay the royalties.
  18. @michellesanver “My code is so awesome, I don’t need tests.”

    - LoneStar PHP conference attendee
  19. @michellesanver Audience Do you use PHPUnit?

  20. @michellesanver Audience TDD? BDD?

  21. @michellesanver First I will explain how the individual parts work

  22. @michellesanver Then how it fits together

  23. @michellesanver Behat + Mink + PhantomJS Gherkin PhantomJS OR webdriver

    MinkContext FeatureContext Behat
  24. @michellesanver What is Behat anyway?

  25. @michellesanver Behat!? Behat is a Behaviour Driven Development framework

  26. @michellesanver Erm, what is BDD, anyway?

  27. @michellesanver BDD BDD is clearly defined behaviour and English spoken

    tests :D
  28. @michellesanver BDD BDD = TDD + Spices

  29. @michellesanver Why we chose Behat

  30. @michellesanver Gherkin Defining your behaviour

  31. @michellesanver Gherkin Feature: List
 In order to use the shoppinglist


    As a website user
 I need to be able to see the shoppinglist and use its elements
 
 @anonymous
 Scenario: Visit shoppinglist page as anonymous
 Given I am not logged in
 And I am on profile “shopping lists"
 Then I should see “My shopping lists"
 And I should see “I don’t have an M-connect account yet“
 
 @loggedin
 Scenario: See the shoppinglist
 Given I am logged in
 And I am on profile “shopping lists"
 Then I should see "My shopping lists"
 And I should see "I don’t have an M-connect account yet"
  32. @michellesanver Gherkin: Feature Feature: List
 In order to use the

    shoppinglist
 As a website user
 I need to be able to see the shoppinglist and use its elements
 
 @anonymous
 Scenario: Visit shoppinglist page as anonymous
 Given I am not logged in
 And I am on profile “shopping lists"
 Then I should see “My shopping lists"
 And I should see “I don’t have an M-connect account yet“
 
 @loggedin
 Scenario: See the shoppinglist
 Given I am logged in
 And I am on profile “shopping lists"
 Then I should see “My shopping lists"
 And I should see “Active lists"
  33. @michellesanver Gherkin: Scenario Feature: List
 In order to use the

    shoppinglist
 As a website user
 I need to be able to see the shoppinglist and use its elements
 
 Scenario: Larry garfield at a conference
 Given I am Larry Garfield
 And I am at a conference
 Then I should wear “Blue shirt”
 And I should wear “Leather vest"
 
 @loggedin
 Scenario: See the shoppinglist
 Given I am logged in
 And I am on profile “shopping lists"
 Then I should see “My shopping lists"
 And I should see “Active lists"
  34. @michellesanver Gherkin Given, Whens, Thens Scenario: Larry garfield at a

    conference
 Given I am Larry Garfield
 And I am at a conference
 Then I should wear “Blue shirt”
 And I should wear “Leather vest"
  35. @michellesanver Gherkin And + But Scenario: Larry garfield at a

    conference
 Given I am Larry Garfield
 And I am at a conference
 Then I should wear “Blue shirt”
 And I should wear “Leather vest"
  36. @michellesanver Gherkin: Background Feature: Multiple site support
 
 Background:
 Given

    a global administrator named "Greg"
 And a blog named "Greg's anti-tax rants"
 And a customer named "Wilson"
 And a blog named "Expensive Therapy" owned by "Wilson"
 
 Scenario: Wilson posts to his own blog
 Given I am logged in as Wilson
 When I try to post to "Expensive Therapy"
 Then I should see "Your article was published."
 
 Scenario: Greg posts to a client's blog
 Given I am logged in as Greg
 When I try to post to "Expensive Therapy"
 Then I should see "Your article was published."
  37. @michellesanver Gherkin: Scenario Outlines Scenario: Drink 5 out of 12


    Given there are 12 beers
 When I drink 5 beers
 Then I should have 7 beers
 
 Scenario: Drink 5 out of 20
 Given there are 20 beers
 When I drink 5 beers
 Then I should have 15 beers
  38. @michellesanver Gherkin: Scenario Outlines Scenario Outline: PHPDay social
 Given there

    are <start> beers
 When I drink <drink> beers
 Then I should have <left> beers
 
 Examples:
 | start | drink | left |
 | 12 | 5 | 7 |
 | 20 | 5 | 15 | Keep it DRY
  39. @michellesanver Gherkin Tagged Hooks (@loggedin etc that we’ve seen)

  40. @michellesanver Reusable Actions: Steps

  41. @michellesanver Steps Each keyword (given, when etc.) is a step

  42. @michellesanver Steps Automatically add steps by accepting context!

  43. @michellesanver Steps use Behat\Behat\Context\SnippetAcceptingContext;
 use Behat\Gherkin\Node\PyStringNode;
 use Behat\Gherkin\Node\TableNode;
 
 class

    FeatureContext implements SnippetAcceptingContext
 {
 /**
 * Initializes context.
 */
 public function __construct()
 {
 }
 }
  44. @michellesanver Steps: Results Pending

  45. @michellesanver Steps: Results Failed

  46. @michellesanver Steps: Results Skipped

  47. @michellesanver Steps: Results Ambigious

  48. @michellesanver Steps: Results Reduntant

  49. @michellesanver Steps: Results Undefined

  50. @michellesanver SUCCESSFUL \o/ :D

  51. @michellesanver Hooked on a feeling *bamdabadam* Hooks

  52. @michellesanver Hooks Suite Hooks

  53. @michellesanver Hooks Feature Hooks

  54. @michellesanver Hooks Scenario Hooks

  55. @michellesanver Hooks Step Hooks

  56. @michellesanver Hooks /** 
 * @BeforeScenario
 */
 public function before(BeforeScenarioScope

    $scope)
 {
 if($this->getMink()->getDefaultSessionName() == 'selenium2') {
 $this->getSession()->resizeWindow(1200, 768);
 } else {
 self::$client = $this->getSession(‘symfony2') ->getDriver()->getClient();
 self::$container = self::$client->getContainer();
 self::$guzzle = self::$container->get(‘guzzle.api.client’);
 }
 }
  57. @michellesanver Tagged hooks /**
 * @BeforeScenario @database,@orm
 */
 public function

    cleanDatabase()
 {
 // clean database before
 // @database OR @orm scenarios
 }
  58. @michellesanver Tagged hooks: && /**
 * @BeforeScenario @database&&@fixtures
 */
 public

    function cleanDatabase()
 {
 // clean database before
 // @database OR @orm scenarios
 }
  59. @michellesanver Context

  60. @michellesanver Context Feature How your application behaves Context How to

    test your application
  61. @michellesanver Context FeatureContext Class

  62. @michellesanver Context /**
 * @When I do something with :argument


    */
 public function iDoSomethingWith($argument)
 {
 // do something with $argument
 }
  63. @michellesanver Context Contact class requirements

  64. @michellesanver Context: Class requirements 1. The context class should implement

    the Behat \Behat\Context\Context interface.
  65. @michellesanver Context: Class requirements 2. The context class should be

    called FeatureContext.
  66. @michellesanver Context: Class requirements 3. The context class should be

    discoverable and loadable by Behat.
  67. @michellesanver Context Contexts lifetime

  68. @michellesanver Context lifetime Your context class is initialized before each

    scenario is run
  69. @michellesanver Context lifetime 1. Every scenario is isolated from each

    other scenario’s context.
  70. @michellesanver Context lifetime 2. Every step in a single scenario

    is executed inside a common context instance.
  71. @michellesanver Context Multiple contexts

  72. @michellesanver Multiple contexts # behat.yml default: suites: default: contexts: -

    FeatureContext - SecondContext - ThirdContext
  73. @michellesanver Writing TESTS

  74. @michellesanver Writing Tests A test only fails if an exception

    is thrown.
  75. @michellesanver Writing Tests Write simple tests using PHPUnits assertions.

  76. @michellesanver Writing Tests But essentially test ANYTHING YOU’D LIKE, just

    throw exceptions.
  77. @michellesanver Mink

  78. @michellesanver Mink Simulate browser behaviour with Mink

  79. @michellesanver Mink Clicking links!

  80. @michellesanver Mink // wait for animation
 $session->wait(500); 
 // get

    loginlink
 $loginLinkSelector = '.hidden-phone a.login';
 $loginLink = $page->find('css', $loginLinkSelector); 
 // click loginlink
 $loginLink->click();
  81. @michellesanver Mink Drag and drop

  82. @michellesanver Fixtures and Mocking

  83. @michellesanver Fixtures and Mocking

  84. @michellesanver Fixtures and Mocking Any time API responses changed… We

    cried. :’(
  85. @michellesanver Fixtures and Mocking PHP VCR Record HTTP interactions!

  86. @michellesanver Fixtures and Mocking PHP VCR Record HTTP interactions and

    replay them during test runs.
  87. @michellesanver PhantomJS Testing JavaScript

  88. @michellesanver PhantomJS Headless browser

  89. @michellesanver Selenium Driver PhantomJS is a ghost!

  90. @michellesanver PhantomJS @javascript
 Scenario: Searching for "tomaten"
 Given I am

    on "/sortiment/groceries"
 When I search for "tomatoes"
 Then I should see “Your search for tomatoes yielded"

  91. @michellesanver PhantomJS /**
 * @When I search for :searchterm
 */


    public function iSearchFor($searchterm)
 {
 
 }
  92. @michellesanver PhantomJS $session = $this->getSession();
 $page = $session->getPage();
 
 $searchField

    = $page->find('css', '.hidden-phone .search-input');
 $searchField->click();
 $searchField->setValue($searchterm);
 
 $searchButton = $page->find('css', '.hidden-phone .search-btn');
 $searchButton->click();
 
 // Wait until we have a search-title with a length longer than 0.
 $session->wait(
 0,
 "$('.search-title').length > 0"
 );
 
 $this->saveScreenshot('debugscreenshot.png', self::DEBUGPATH);

  93. @michellesanver PhantomJS $session = $this->getSession();
 $page = $session->getPage();
 
 $searchField

    = $page->find('css', '.hidden-phone .search-input');
 $searchField->click();
 $searchField->setValue($searchterm);
 
 $searchButton = $page->find('css', '.hidden-phone .search-btn');
 $searchButton->click();
 
 // Wait until we have a search-title with a length longer than 0.
 $session->wait(
 0,
 "$('.search-title').length > 0"
 );
 
 $this->saveScreenshot('debugscreenshot.png', self::DEBUGPATH);

  94. @michellesanver PhantomJS $session = $this->getSession();
 $page = $session->getPage();
 
 $searchField

    = $page->find('css', '.hidden-phone .search-input');
 $searchField->click();
 $searchField->setValue($searchterm);
 
 $searchButton = $page->find('css', '.hidden-phone .search-btn');
 $searchButton->click();
 
 // Wait until we have a search-title with a length longer than 0.
 $session->wait(
 0,
 "$('.search-title').length > 0"
 );
 
 $this->saveScreenshot('debugscreenshot.png', self::DEBUGPATH);

  95. @michellesanver PhantomJS $session = $this->getSession();
 $page = $session->getPage();
 
 $searchField

    = $page->find('css', '.hidden-phone .search-input');
 $searchField->click();
 $searchField->setValue($searchterm);
 
 $searchButton = $page->find('css', '.hidden-phone .search-btn');
 $searchButton->click();
 
 // Wait until we have a search-title with a length longer than 0.
 $session->wait(
 0,
 "$('.search-title').length > 0"
 );
 
 $this->saveScreenshot('debugscreenshot.png', self::DEBUGPATH);

  96. @michellesanver PhantomJS $session = $this->getSession();
 $page = $session->getPage();
 
 $searchField

    = $page->find('css', '.hidden-phone .search-input');
 $searchField->click();
 $searchField->setValue($searchterm);
 
 $searchButton = $page->find('css', '.hidden-phone .search-btn');
 $searchButton->click();
 
 // Wait until we have a search-title with a length longer than 0.
 $session->wait(
 0,
 "$('.search-title').length > 0"
 );
 
 $this->saveScreenshot('debugscreenshot.png', self::DEBUGPATH);

  97. @michellesanver Screenshot comparison Compare image code

  98. @michellesanver Fitting the pieces together

  99. @michellesanver Behat + Mink + PhantomJS Gherkin PhantomJS OR webdriver

    MinkContext FeatureContext Behat
  100. @michellesanver Wrapup

  101. @michellesanver Behat and Gherkin A BDD Framework with clearly defined

    behaviours
  102. @michellesanver Contexts and Steps Steps are your tests living in

    a context, which contains your logic for testing
  103. @michellesanver Testing JavaScript Use Mink with a driver, for instance

    PhantomJS to test dynamic parts of your website.
  104. @michellesanver Still not mature for CSS and image comparison.

  105. @michellesanver Behat is Open Source

  106. @michellesanver Got a bit excited about one Testing Framework for

    ALL THE THINGS? As developers in an open source community the decision is ours.
  107. @michellesanver Contribute How would you use Behat in Drupal? Any

    caveats you can think of?
  108. @michellesanver Open Source Open Discussion

  109. @michellesanver Thank you!