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

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

Antonis Pavlakis

October 02, 2015
Tweet

More Decks by Antonis Pavlakis

Other Decks in Programming

Transcript

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

    View Slide

  2. Long live the legacy
    For refactoring, read
    this book … >>>
    https://leanpub.com/mlaphp

    View Slide

  3. No refactoring needed
    (not today)

    View Slide

  4. Today is black-box testing day!

    View Slide

  5. Let’s get started

    View Slide

  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

    View Slide

  7. Getting started
    (1) git clone [email protected]: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

    View Slide

  8. setting up Behat
    Step 1
    composer install

    View Slide

  9. setting up Behat
    Create the directory structure.
    From inside the /var/www/tests/behat/ folder,
    run:
    > ../../vendor/bin/behat --init
    Step 2

    View Slide

  10. setting up Behat
    Create behat.yml
    /var/www/tests/behat/behat.yml
    Step 3

    View Slide

  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

    View Slide

  12. Directory
    Structure

    View Slide

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

    View Slide

  14. Features & Scenarios

    View Slide

  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.

    View Slide

  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

    View Slide

  17. Enough theory.
    Let’s get started with the first test.

    View Slide

  18. “Scenario: The homepage loads”

    View Slide

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

    View Slide

  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

    View Slide

  21. Run the test
    Connect to the box: vagrant ssh
    Run behat: legacybehat

    View Slide

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

    View Slide

  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

    View Slide

  24. “Scenario: I can create an album”

    View Slide

  25. “Scenario: I can create an album”
    • Possible steps:
    • Navigate to the “create” page
    • Add artist
    • Add album
    • Submit form

    View Slide

  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

    View Slide

  27. “Scenario: I can create an album”
    tests/behat/features/bootstrap/album.feature
    Guidance.
    Use the following definitions:
    And /^(?:|I )follow "(?P(?:[^"]|\\")*)"$/
    And /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P(?:[^"]|\\")*)"$/
    And /^(?:|I )press "(?P(?:[^"]|\\")*)"$/

    View Slide

  28. Exercise #3
    “Scenario: I can delete an album”

    View Slide

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

    View Slide

  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

    View Slide

  31. Contexts

    View Slide

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

    View Slide

  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

    View Slide

  34. Not part of the available definitions
    list!

    View Slide

  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

    View Slide

  36. Custom step definition
    /**

    * @Given I am on the add page

    */

    public function iAmOnTheAddPage()

    {

    throw new PendingException();

    }

    tests/behat/features/bootstrap/FeatureContext.php

    View Slide

  37. Custom step definition
    /**

    * @Given I am on the add page

    */

    public function iAmOnTheAddPage()

    {

    $this->visit('/index/add');

    }
    tests/behat/features/bootstrap/FeatureContext.php

    View Slide

  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

    View Slide

  39. Exercise #5
    Making the story more readable

    View Slide

  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

    View Slide

  41. Exercise #6
    Guidance.
    Use the following methods:
    $this->fillField($field, $value);
    $this->pressButton($button);
    $this->assertPageContainsText($text);
    tests/behat/features/bootstrap/FeatureContext.php

    View Slide

  42. Traversing Elements
    Making sure you’re accessing the
    element you want.
    (Following, are examples from the http://mink.behat.org/ docs)

    View Slide

  43. Selectors
    css selector
    Using CSS expressions to search the DOM
    $title = $page->find('css', 'h1');
    $buttonIcon = $page->find('css', '.btn > .icon');

    View Slide

  44. Selectors
    xpath selector
    Using path queries to search the DOM
    $anchorsWithoutUrl = $page->findAll('xpath', '//
    a[not(@href)]');

    View Slide

  45. Exercise #7
    Using the CSS selector
    (1) Edit a specific album
    (2) Delete a specific album

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  49. Hooks

    View Slide

  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.”

    View Slide

  51. Before Feature
    Run some logic before we start running the scenarios.

    View Slide

  52. Before Feature
    use Behat\Behat\Hook\Scope\BeforeFeatureScope;

    /** @BeforeFeature */

    public static function prepareForTheFeature(BeforeFeatureScope $scope)

    {


    }
    tests/behat/features/bootstrap/FeatureContext.php

    View Slide

  53. Clean DB
    use Behat\Behat\Hook\Scope\BeforeFeatureScope;

    /** @BeforeFeature */

    public static function cleanDB(BeforeFeatureScope $scope)

    {


    }
    tests/behat/features/bootstrap/FeatureContext.php

    View Slide

  54. Testing legacy apps?
    Roll up your sleeves…

    View Slide

  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

    View Slide

  56. No need to hardcode the credentials.

    View Slide

  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

    View Slide

  58. Context Parameters

    public function __construct($username, $password, $dbName)

    {

    }

    tests/behat/features/bootstrap/FeatureContext.php

    View Slide

  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

    View Slide

  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]

    View Slide

  61. Exercise #8
    (1) Create a @BeforeFeature to populate
    with test data (fixtures/seeders)
    (2) Create an @AfterFeature to clean DB

    View Slide

  62. Exercise #9
    (1) Create a PDO object
    (2) Create an album. Use PDO to
    verify it exists in the DB

    View Slide

  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

    View Slide

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

    View Slide

  65. Exercise #11
    (1) Create 10 albums through the UI
    (2) Delete 5 albums.
    (3) Verify they have been deleted in
    the DB.

    View Slide

  66. Tags

    View Slide

  67. Tags
    “Tags are a great way to organise your features and
    scenarios”
    - docs.behat.org

    View Slide

  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.

    View Slide

  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

    View Slide

  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.

    View Slide

  71. “A Solution…”
    default:

    suites:

    default:

    contexts:

    - FeatureContext:

    - root

    - Admin123

    - zf-tutorial-test

    filters:

    tags: @defaultAlbumFixture,@db
    /var/www/tests/behat/behat.yml

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  75. Thanks
    Antonis Pavlakis
    @pavlakis
    https://joind.in/talk/view/15420
    https://github.com/pavlakis/phpnw15-behat-tutorial

    View Slide