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