Test legacy apps with Behat

Test legacy apps with Behat

A tutorial given at PHPNW15 - October 2nd 2015. All references and examples use the following repository and Vagrant VM environment: https://github.com/pavlakis/phpnw15-behat-tutorial

8234e593787784e12761320a7dca363a?s=128

Antonis Pavlakis

October 02, 2015
Tweet

Transcript

  1. Test legacy apps with Behat Antonis Pavlakis (@pavlakis) phpnw15 tutorial

    day
  2. Long live the legacy For refactoring, read this book …

    >>> https://leanpub.com/mlaphp
  3. No refactoring needed (not today)

  4. Today is black-box testing day!

  5. Let’s get started

  6. The environment • Legacy code - ZF1 (1.10.6) tutorial by

    Rob Allen (@akrabat) • Basic LAMP box with Ubuntu 12.04, PHP 5.5, MySQL 5.5
  7. Getting started (1) git clone git@github.com:pavlakis/phpnw15-behat-tutorial.git (2) vagrant up (3)

    Add the following to your hosts file: 192.168.42.200 dev.legacy.com test.legacy.com
  8. setting up Behat Step 1 composer install

  9. setting up Behat Create the directory structure. From inside the

    /var/www/tests/behat/ folder, run: > ../../vendor/bin/behat --init Step 2
  10. setting up Behat Create behat.yml /var/www/tests/behat/behat.yml Step 3

  11. Step 3 default:
 suites:
 default:
 contexts:
 - FeatureContext
 
 extensions:


    
 Behat\MinkExtension:
 browser_name: phantomjs
 javascript_session: selenium2
 base_url: http://test.legacy.com/
 
 sessions:
 my_session:
 goutte:
 guzzle_parameters:
 verify: false
 
 selenium2:
 wd_host: "http://localhost:8643/wd/hub"
 capabilities: { "browser": "phantomjs" } Ensure the URL is accessible from your server. (e.g. in your hosts file) /var/www/tests/behat/behat.yml
  12. Directory Structure

  13. setting up Behat Run: /var/www/vendor/bin/behat --config=/var/www/ tests/behat/behat.yml -f progress Step

    4
  14. Features & Scenarios

  15. Features & Scenarios Behat tests live inside .feature files Behat

    tests are called Scenarios Behat Scenarios use the Gherkin language, which allows us to write English statements.
  16. Definitions A statement from a scenario that maps to a

    PHP method and identified through the method’s doc-block. For available definitions, run: $ ./behat --config=/var/www/tests/behat/behat.yml -dl
  17. Enough theory. Let’s get started with the first test.

  18. “Scenario: The homepage loads”

  19. “Scenario: The homepage loads” Create file: album.feature tests/behat/features/bootstrap/album.feature

  20. “Scenario: The homepage loads” Feature: Albums
 I can create, edit

    and delete albums 
 Scenario: The homepage loads
 Given I am on the homepage
 Then I should see text matching "My Album" tests/behat/features/bootstrap/album.feature
  21. Run the test Connect to the box: vagrant ssh Run

    behat: legacybehat
  22. Exercise #1 “Scenario: Page does not exist”

  23. “Scenario: Page does not exist” Feature: Albums
 I can create,

    edit and delete albums
 
 Scenario: The homepage loads
 Given I am on the homepage
 Then I should see text matching "My Album” Scenario: Page does not exist Given I …
 Then I … tests/behat/features/bootstrap/album.feature
  24. “Scenario: I can create an album”

  25. “Scenario: I can create an album” • Possible steps: •

    Navigate to the “create” page • Add artist • Add album • Submit form
  26. “Scenario: I can create an album” Feature: Albums
 I can

    create, edit and delete albums
 
 Scenario: The homepage loads
 Given I am on the homepage
 Then I should see text matching “My Album” Scenario: Page does not exist Given I …
 Then I … Scenario: I can create an album
 Given I am on the homepage
 And I follow … tests/behat/features/bootstrap/album.feature
  27. “Scenario: I can create an album” tests/behat/features/bootstrap/album.feature Guidance. Use the

    following definitions: And /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/ And /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/ And /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/
  28. Exercise #3 “Scenario: I can delete an album”

  29. Exercise #4 “Scenario: I can edit an album”

  30. “A solution…” Feature: Albums
 I can create, edit and delete

    albums
 
 Scenario: The homepage loads
 Given I am on the homepage
 Then I should see text matching "My Album"
 
 Scenario: Page does not exist
 Given I am on "/notexist"
 Then I should see text matching "Page not found"
 
 Scenario: Can create an album
 Given I am on the homepage
 And I follow "Add new album"
 And I fill in "artist" with "Queen"
 And I fill in "title" with “A kind of magic"
 And I press "Add"
 Then I should see text matching "A kind of magic"
 And I should be on the homepage
 
 Scenario: Can edit an album
 Given I am on the homepage
 And I follow "Edit"
 And I fill in "artist" with “Billy Joel"
 And I fill in "title" with “An Innocent Man"
 And I press "Save"
 Then I should see text matching “An Innocent Man"
 And I should be on the homepage tests/behat/features/bootstrap/album.feature
  31. Contexts

  32. Contexts A PHP file that tells Behat how to run

    our tests.
  33. “Can create a default album” 
 
 Scenario: Can create

    a default album
 Given I am on the add page
 
 
 tests/behat/features/bootstrap/album.feature
  34. Not part of the available definitions list!

  35. Custom step $ legacybehat ..................U------ 5 scenarios (4 passed, 1

    undefined) 25 steps (18 passed, 1 undefined, 6 skipped) 0m0.28s (14.25Mb) --- FeatureContext has missing steps. Define them with these snippets: /** * @Given I am on the add page */ public function iAmOnTheAddPage() { throw new PendingException(); } terminal
  36. Custom step definition /**
 * @Given I am on the

    add page
 */
 public function iAmOnTheAddPage()
 {
 throw new PendingException();
 }
 tests/behat/features/bootstrap/FeatureContext.php
  37. Custom step definition /**
 * @Given I am on the

    add page
 */
 public function iAmOnTheAddPage()
 {
 $this->visit('/index/add');
 } tests/behat/features/bootstrap/FeatureContext.php
  38. First iteration Scenario: Can create a default album
 Given I

    am on the add page
 And I fill in "artist" with "test123"
 And I fill in "title" with "test321"
 And I press "Add"
 Then I should see text matching "test123"
 And I should be on the homepage tests/behat/features/bootstrap/FeatureContext.php
  39. Exercise #5 Making the story more readable

  40. Second iteration Scenario: Can create a default album
 Given I

    am on the add page
 And I create a default album
 Then The album has been created tests/behat/features/bootstrap/FeatureContext.php
  41. Exercise #6 Guidance. Use the following methods: $this->fillField($field, $value); $this->pressButton($button);

    $this->assertPageContainsText($text); tests/behat/features/bootstrap/FeatureContext.php
  42. Traversing Elements Making sure you’re accessing the element you want.

    (Following, are examples from the http://mink.behat.org/ docs)
  43. Selectors css selector Using CSS expressions to search the DOM

    $title = $page->find('css', 'h1'); $buttonIcon = $page->find('css', '.btn > .icon');
  44. Selectors xpath selector Using path queries to search the DOM

    $anchorsWithoutUrl = $page->findAll('xpath', '// a[not(@href)]');
  45. Exercise #7 Using the CSS selector (1) Edit a specific

    album (2) Delete a specific album
  46. A starting point $page = $this->getMink()->getSession()->getPage(); tests/behat/features/bootstrap/FeatureContext.php

  47. Can edit a specific album /**
 * @Given I go

    to edit album by title :title
 */
 public function iGoToEditAlbumByTitle($title)
 {
 $page = $this->getMink()->getSession()->getPage();
 
 $editLink = $page->find(…);
 
 $this->visit($editLink);
 } tests/behat/features/bootstrap/FeatureContext.php
  48. Can edit a specific album /**
 * @Given I go

    to edit album by title :title
 */
 public function iGoToEditAlbumByTitle($title)
 {
 $page = $this->getMink()->getSession()->getPage();
 
 $editLink = $page->find(
 'css',
 sprintf('table tr td:contains("%s")', $title)
 )
 ->getParent()
 ->findLink('Edit')
 ->getAttribute('href');
 
 $this->visit($editLink);
 } tests/behat/features/bootstrap/FeatureContext.php
  49. Hooks

  50. Hooks From docs.behat.org “Behat provides a number of hook points

    which allow us to run arbitrary logic at various points in the Behat test cycle.”
  51. Before Feature Run some logic before we start running the

    scenarios.
  52. Before Feature use Behat\Behat\Hook\Scope\BeforeFeatureScope; … /** @BeforeFeature */
 public static

    function prepareForTheFeature(BeforeFeatureScope $scope)
 {
 
 } tests/behat/features/bootstrap/FeatureContext.php
  53. Clean DB use Behat\Behat\Hook\Scope\BeforeFeatureScope; … /** @BeforeFeature */
 public static

    function cleanDB(BeforeFeatureScope $scope)
 {
 
 } tests/behat/features/bootstrap/FeatureContext.php
  54. Testing legacy apps? Roll up your sleeves…

  55. Clean DB #1 use Behat\Behat\Hook\Scope\BeforeFeatureScope; … /** @BeforeFeature */
 public

    static function cleanDB(BeforeFeatureScope $scope)
 {
 
 // prepare system for the feature
 $username = 'root';
 $password = 'Admin123';
 $database = 'zf-tutorial-test';
 shell_exec("sh /var/www/scripts/db.sh clean {$username} {$password} {$database}");
 } tests/behat/features/bootstrap/FeatureContext.php
  56. No need to hardcode the credentials.

  57. default:
 suites:
 default:
 contexts:
 - FeatureContext:
 - root
 - Admin123


    - zf-tutorial-test
 
 extensions:
 
 Behat\MinkExtension:
 browser_name: phantomjs
 javascript_session: selenium2
 base_url: http://test.legacy.com/
 
 sessions:
 my_session:
 goutte:
 guzzle_parameters:
 verify: false
 
 selenium2:
 wd_host: "http://localhost:8643/wd/hub"
 capabilities: { "browser": "phantomjs" } /var/www/tests/behat/behat.yml /var/www/tests/behat/ features/bootstrap/ FeatureContext.php
  58. Context Parameters … public function __construct($username, $password, $dbName)
 {
 }

    … tests/behat/features/bootstrap/FeatureContext.php
  59. Clean DB #2 use Behat\Behat\Hook\Scope\BeforeFeatureScope; … /** @BeforeFeature */
 public

    static function cleanDB(BeforeFeatureScope $scope)
 {
 
 // prepare system for the feature
 $dbCreds = $scope->getSuite()->getSetting("contexts")[0]["FeatureContext"];
 
 shell_exec("sh /var/www/scripts/db.sh clean {$dbCreds[0]} {$dbCreds[1]} {$dbCreds[2]}");
 } tests/behat/features/bootstrap/FeatureContext.php
  60. Available Hooks BeforeSuite [Behat\Testwork\Hook\Scope\BeforeSuiteScope] AfterSuite [Behat\Testwork\Hook\Scope\AfterSuiteScope] BeforeFeature [Behat\Behat\Hook\Scope\BeforeFeatureScope] AfterFeature [Behat\Behat\Hook\Scope\AfterFeatureScope]

    BeforeScenario [Behat\Behat\Hook\Scope\BeforeScenarioScope] AfterScenario [Behat\Behat\Hook\Scope\AfterScenarioScope] BeforeStep [Behat\Behat\Hook\Scope\BeforeStepScope] AfterStep [Behat\Behat\Hook\Scope\AfterStepScope]
  61. Exercise #8 (1) Create a @BeforeFeature to populate with test

    data (fixtures/seeders) (2) Create an @AfterFeature to clean DB
  62. Exercise #9 (1) Create a PDO object (2) Create an

    album. Use PDO to verify it exists in the DB
  63. Connect to DB public function __construct($username, $password, $dbName)
 {
 $this->db

    = new \PDO("mysql:host=127.0.0.1;dbname={$dbName}", $username, $password);
 } tests/behat/features/bootstrap/FeatureContext.php
  64. Exercise #10 (1) Create 10 albums through the UI (2)

    Verify they exist in the DB
  65. Exercise #11 (1) Create 10 albums through the UI (2)

    Delete 5 albums. (3) Verify they have been deleted in the DB.
  66. Tags

  67. Tags “Tags are a great way to organise your features

    and scenarios” - docs.behat.org
  68. Tags default:
 suites:
 default:
 contexts:
 - FeatureContext:
 - root
 -

    Admin123
 - zf-tutorial-test
 filters:
 tags: @db /var/www/tests/behat/behat.yml Add more using a comma-separated format.
  69. Tags - Features and Scenarios @db Feature: Albums
 I can

    create, edit and delete albums
 
 Scenario: Can create an album
 Given I am on the homepage
 And I follow "Add new album"
 And I fill in "artist" with "Queen"
 And I fill in "title" with “A kind of magic"
 And I press "Add"
 Then I should see text matching "A kind of magic"
 And I should be on the homepage
 @db
 Scenario: Can edit an album
 Given I am on the homepage
 And I follow "Edit"
 And I fill in "artist" with “Billy Joel"
 And I fill in "title" with “An Innocent Man"
 And I press "Save"
 Then I should see text matching “An Innocent Man"
 And I should be on the homepage tests/behat/features/bootstrap/album.feature
  70. Exercise #12 (1) Create a tag with a fixture to

    populate DB with a default album. (2) Create a scenario that edits the above album.
  71. “A Solution…” default:
 suites:
 default:
 contexts:
 - FeatureContext:
 - root


    - Admin123
 - zf-tutorial-test
 filters:
 tags: @defaultAlbumFixture,@db /var/www/tests/behat/behat.yml
  72. “A Solution…” @db Feature: Albums
 I can create, edit and

    delete albums
 
 Scenario: Can create an album
 Given I am on the homepage
 And I follow "Add new album"
 And I fill in "artist" with "Queen"
 And I fill in "title" with “A kind of magic"
 And I press "Add"
 Then I should see text matching "A kind of magic"
 And I should be on the homepage
 @defaultAlbumFixture
 Scenario: Can edit an album
 Given I am on the homepage
 And I follow "Edit"
 And I fill in "artist" with “Billy Joel"
 And I fill in "title" with “An Innocent Man"
 And I press "Save"
 Then I should see text matching “An Innocent Man"
 And I should be on the homepage tests/behat/features/bootstrap/album.feature
  73. “A Solution…” /**
 * @BeforeScenario @defaultAlbumFixture
 */
 public static function

    populateDB(BeforeScenarioScope $scope)
 {
 $dbCreds = $scope->getSuite()->getSetting("contexts")[0]["FeatureContext"];
 
 shell_exec("sh /var/www/scripts/db.sh populate {$dbCreds[0]} {$dbCreds[1]} {$dbCreds[2]}");
 }
 
 /**
 * @BeforeFeature @db
 */
 public static function cleanDB(BeforeFeatureScope $scope)
 {
 $dbCreds = $scope->getSuite()->getSetting("contexts")[0]["FeatureContext"];
 
 
 shell_exec("sh /var/www/scripts/db.sh clean {$dbCreds[0]} {$dbCreds[1]} {$dbCreds[2]}");
 } tests/behat/features/bootstrap/FeatureContext.php
  74. Credits • Rob Allen’s (@akrabat) ZF1 tutorial (http://akrabat.com/zend- framework-tutorial/ )

    • Behat step definitions: http://docs.behat.org/en/v3.0/guides/ 2.definitions.html • Behat contexts: http://docs.behat.org/en/v3.0/guides/ 4.contexts.html • Traversing elements: http://mink.behat.org/en/latest/guides/ traversing-pages.html • Behat hooks: http://docs.behat.org/en/v3.0/guides/3.hooks.html
  75. Thanks Antonis Pavlakis @pavlakis https://joind.in/talk/view/15420 https://github.com/pavlakis/phpnw15-behat-tutorial