Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

No refactoring needed (not today)

Slide 4

Slide 4 text

Today is black-box testing day!

Slide 5

Slide 5 text

Let’s get started

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

setting up Behat Step 1 composer install

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Directory Structure

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

Features & Scenarios

Slide 15

Slide 15 text

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.

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

“Scenario: The homepage loads”

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

“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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Exercise #1 “Scenario: Page does not exist”

Slide 23

Slide 23 text

“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

Slide 24

Slide 24 text

“Scenario: I can create an album”

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

“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

Slide 27

Slide 27 text

“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(?:[^"]|\\")*)"$/

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

“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

Slide 31

Slide 31 text

Contexts

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

“Can create a default album” 
 
 Scenario: Can create a default album
 Given I am on the add page
 
 
 tests/behat/features/bootstrap/album.feature

Slide 34

Slide 34 text

Not part of the available definitions list!

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Custom step definition /**
 * @Given I am on the add page
 */
 public function iAmOnTheAddPage()
 {
 $this->visit('/index/add');
 } tests/behat/features/bootstrap/FeatureContext.php

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Exercise #5 Making the story more readable

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Hooks

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Testing legacy apps? Roll up your sleeves…

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

No need to hardcode the credentials.

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

Context Parameters … public function __construct($username, $password, $dbName)
 {
 } … tests/behat/features/bootstrap/FeatureContext.php

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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]

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

Tags

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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.

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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.

Slide 71

Slide 71 text

“A Solution…” default:
 suites:
 default:
 contexts:
 - FeatureContext:
 - root
 - Admin123
 - zf-tutorial-test
 filters:
 tags: @defaultAlbumFixture,@db /var/www/tests/behat/behat.yml

Slide 72

Slide 72 text

“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

Slide 73

Slide 73 text

“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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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