WORKSHOPDAG 27 APRIL 2013 Automatiseren van functionele tests met Behat en Mink &
VOORBEREIDING• Download en installeer Virtualbox (https://www.virtualbox.org/)
• Download en installeer Vagrant (http://www.vagrantup.com/)
• https://github.com/pfznl/wsd13-functionaltesting/
• $ git clone https://github.com/pfznl/wsd13-functionaltesting.git
• $ cd wsd13-functionaltesting
• $ vagrant up
• ???
• Profit
WAT HEB JE NODIG?• Computer/laptop
• PHP 5.3
• [Java] (als je selenium wilt draaien)
• Virtualbox
• [Vagrant]
https://github.com/pfznl/wsd13-functionaltesting/
EVEN VOORSTELLEN
• @Richard_Tuin
• Software ontwikkelaar
• Speciale interesse in kwaliteit
• Werkzaam bij Enrise
HET PROBLEEM
• Verschillend beeld op scope/werking van de oplevering
• De klant weet soms achteraf pas hoe het systeem precies gebruikt kan worden
• “Het is wel mooi, maar zou x toch y kunnen werken?”
• “Bedankt voor de nieuwe feature, maar nu werkt x niet meer”
HET PROBLEEM
• Verschillend beeld op scope/werking van de oplevering
• De klant weet soms achteraf pas hoe het systeem precies gebruikt kan worden
• “Het is wel mooi, maar zou x toch y kunnen werken?”
• “Bedankt voor de nieuwe feature, maar nu werkt x niet meer”
WANT EIGENLIJK...
• ... hebben we een gezamenlijk doel
• ... willen we wat we gaan bouwen zo nauwkeurig mogelijk definiëren
• ... willen we constante kwaliteit leveren
DAT BETEKENT DAT...
• ... we beter, en op gelijk niveau moeten communiceren
• ... we een documentatiesysteem moeten bedenken voor de afstemming
• ... we regelmatig moeten valideren dat wat we leveren nog volgens specificaties is
DAT BETEKENT DAT...
• ... we beter, en op gelijk niveau moeten communiceren
• ... we een documentatiesysteem moeten bedenken voor de afstemming
• ... we regelmatig moeten valideren dat wat we leveren nog volgens specificaties is
Automatiseren?
AGILE MANIFESTO
Mensen en hun onderlinge interactie > processen and tools
Werkende software > allesomvattende documentatie
Samenwerking met de klant > contractonderhandelingen
Inspelen op verandering > het volgen van een plan
OPSTELLEN VAN REQUIREMENTS
• Omschrijven hoe een probleem opgelost wordt
• Gezamenlijk met de klant opstellen
• In de taal (technische) van de klant
• Functioneel gericht, niet technisch gericht
Ik wil graag een zoekmachine bouwen, zodat mijn bezoekers het hele internet kunnen
doorzoeken!
Wow, leuke klus!Ik ga direct aan de slag
Alstublieft! Hier is de zoekmachine, mooi hè?
Jawel, mooie foto op de achtergrond. En het zoeken werkt, maar ik bedoelde ook dat bezoekers op afbeeldingen kunnen
zoeken!
Alstublieft! Hier is de zoekmachine, mooi hè?
Jawel, mooie foto op de achtergrond. En het zoeken werkt, maar ik bedoelde ook dat bezoekers op afbeeldingen kunnen
zoeken!
Ja zeg, daar kom je nu mee
Alstublieft! Hier is de zoekmachine, mooi hè?
Jawel, mooie foto op de achtergrond. En het zoeken werkt, maar ik bedoelde ook dat bezoekers op afbeeldingen kunnen
zoeken!
Ja zeg, daar kom je nu mee
STAPPENPLAN
1. Omschrijf de feature in één zin
2. Omschrijf de intentie
3. Schrijf realistische scenario’s
PRAKTIJK
Feature: {feature omschrijving}
{intentie}As a {personage(s)}I want {feature}So that {intentie}
Scenario: {scenario omschrijving}Given {context} And {meer context}When {actie}Then {resultaat}
Scenario: ...
Informatie: http://dannorth.net/whats-in-a-story/
VOORBEELD ZOEKFUNCTIEFeature: Search on the internet As a bing.com visitor I want to use the search engine So that i can find information on the internet
Scenario: Simple keyword search Given I am on the homepage When I search the term “PHP” Then I should see search results containing “PHP”
LEVENDE DOCUMENTATIE
• Alle features en scenario’s bij elkaar zijn de documentatie
• Bij een change request werk je deze documentatie daarom bij
COMPONENTEN VAN EEN FUNCTIONELE TESTSUITE
• Features
• Feature parser
• Browser controller client
• Browser controller/simulator
• Stappen programmeren met browser automatisering
COMPONENTEN VAN EEN FUNCTIONELE TESTSUITE
• Features
• Feature parser
• Browser controller client
• Browser controller/simulator
• Stappen programmeren met browser automatisering
Documentatie
COMPONENTEN VAN EEN FUNCTIONELE TESTSUITE
• Features
• Feature parser
• Browser controller client
• Browser controller/simulator
• Stappen programmeren met browser automatisering
Documentatie
Behat
COMPONENTEN VAN EEN FUNCTIONELE TESTSUITE
• Features
• Feature parser
• Browser controller client
• Browser controller/simulator
• Stappen programmeren met browser automatisering
Documentatie
Behat
Mink
COMPONENTEN VAN EEN FUNCTIONELE TESTSUITE
• Features
• Feature parser
• Browser controller client
• Browser controller/simulator
• Stappen programmeren met browser automatisering
Documentatie
Behat
Mink
Selenium, Sahi, Goutte, etc.
COMPONENTEN VAN EEN FUNCTIONELE TESTSUITE
• Features
• Feature parser
• Browser controller client
• Browser controller/simulator
• Stappen programmeren met browser automatisering
Documentatie
Behat
Mink
Selenium, Sahi, Goutte, etc.
MinkExtension
INSTALLEREN VAN BEHAT1. Maak een map genaamd “testsuite”2. Installeer composer (http://docs.behat.org/quick_intro.html#installation)
3. Maak een bestand composer.json met de volgende inhoud:
4. $ php composer.phar install
{ "require": { "behat/behat": "2.4.*@stable" }, "minimum-stability": "dev", "config": { "bin-dir": "bin/" }}
$ curl http://getcomposer.org/installer | php
INSTALLEREN VAN BEHAT1. Maak een map genaamd “testsuite”2. Installeer composer (http://docs.behat.org/quick_intro.html#installation)
3. Maak een bestand composer.json met de volgende inhoud:
4. $ php composer.phar install
{ "require": { "behat/behat": "2.4.*@stable" }, "minimum-stability": "dev", "config": { "bin-dir": "bin/" }}
That’s it!
$ curl http://getcomposer.org/installer | php
HELLO BEHAT
1. Initialiseer een Behat testsuite met het commando: $ bin/behat --init
2. Behat heeft de volgende mappen en bestanden aangemaakt:
3. Run de testsuite: $ bin/behat
STEPS? (TERMINOLOGIE)
Feature: {feature omschrijving}
{intentie}As a {personage(s)}I want {feature}So that {intentie}
Scenario: {scenario omschrijving}Given {context} And {meer context}When {actie}Then {resultaat}
Scenario: ...
STEPS? (TERMINOLOGIE)
Feature: {feature omschrijving}
{intentie}As a {personage(s)}I want {feature}So that {intentie}
Scenario: {scenario omschrijving}Given {context} And {meer context}When {actie}Then {resultaat}
Scenario: ...
Feature, user story, module
STEPS? (TERMINOLOGIE)
Feature: {feature omschrijving}
{intentie}As a {personage(s)}I want {feature}So that {intentie}
Scenario: {scenario omschrijving}Given {context} And {meer context}When {actie}Then {resultaat}
Scenario: ...
Feature, user story, module
Scenario
STEPS? (TERMINOLOGIE)
Feature: {feature omschrijving}
{intentie}As a {personage(s)}I want {feature}So that {intentie}
Scenario: {scenario omschrijving}Given {context} And {meer context}When {actie}Then {resultaat}
Scenario: ...
Feature, user story, module
Scenario
Steps
UITBREIDING COMPOSER.JSON{ "require": { "behat/behat": "2.4.*@stable", "behat/mink-extension": "*", "behat/mink": "*", "behat/mink-selenium2-driver": "*", "behat/mink-goutte-driver": "*", "behat/mink-zombie-driver": "*", "behat/mink-sahi-driver": "*" }, "minimum-stability": "dev", "config": { "bin-dir": "bin/" }}
$ composer.phar update
VOORBEELD ZOEKFUNCTIE
Feature: Search on the internet As a bing.com visitor I want to use the search engine So that i can find information on the internet
Scenario: Simple keyword search Given I am on the homepage When I search the term “PHP” Then I should see search results containing “PHP”
features/search.feature
STEP DEFINITIES/** * @Given /^I am on the homepage$/ */public function iAmOnTheHomepage(){ throw new PendingException();}
/** * @When /^I search the term "([^"]*)"$/ */public function iSearchTheTerm($arg1){ throw new PendingException();}
Deze kun je in FeatureContext.php plaatsen, echter...
MINKEXTENSION1. Is een set van (basis) voorgedefinieerde steps
2. Maakt gebruik van Mink
3. Maar... niet alle teksten van de steps zijn even bruikbaar
/** * Opens homepage. * * @Given /^(?:|I )am on (?:|the )homepage$/ * @When /^(?:|I )go to (?:|the )homepage$/ */public function iAmOnHomepage(){ $this->getSession()->visit($this->locatePath('/'));}
MINK TERMINOLOGIE
• Driver = Browser controller/emulator• Session = Browser• Page = Document(Element)
• Element• Selectors
• XPath• CSS• Named
./BEHAT.YMLdefault: extensions: Behat\MinkExtension\Extension: default_session: goutte goutte: ~ base_url: "http://www.bing.com" context: parameters: foo: "bar"
En dan....
$ bin/behat
MINK STEPSGiven /^(?:|I )am on (?:|the )homepage$/When /^(?:|I )go to (?:|the )homepage$/Given /^(?:|I )am on "(?P<page>[^"]+)"$/When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/Then /^(?:|I )should be on "(?P<page>[^"]+)"$/Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/
$ bin/behat -dl
/** * Clicks link with specified id|title|alt|text. * * @When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/ */public function clickLink($link){ $link = $this->fixStepArgument($link); $this->getSession()->getPage()->clickLink($link);}
FEATURECONTEXT.PHP
• Alles wat je in beginsel nodig hebt
• De class voor je:
• Step definitions
• Hooks
• Initialisatie
STEP DEFINITIES MAKEN/** * @When /^I search the term "([^"]*)"$/ */public function iSearchTheTerm($searchTerm){ $this->fillField('q', $searchTerm); // Mink definities $this->pressButton('go');}
/** * @When /^I search the term "([^"]*)"$/ */public function iSearchTheTerm($searchTerm){ $page = $this->getSession()->getPage(); $page->fillField('q', $searchTerm); $page->pressButton('go');}
SELECTORS
• Selecteren van elementen
• XPath, CSS, Names$elements = $page->findAll('xpath', './/div[@class="sa_mc"]');$elements = $page->findAll('css', 'div[class="sa_mc"]');$elements = $page->findAll('named', array('link', 'Register');$elements = $page->findAll('named', array('button', 'Search');
DE LAATSTE STAPThen I should see search results containing “PHP”
/** * @Then /^I should see search results containing (.*)$/ */public function iShouldSeeSearchResultsContaining($searchTerm){ $searchTerm = quotemeta($searchTerm); $regex = sprintf('/%s/mi', $searchTerm);
$page = $this->getSession()->getPage(); $elements = $page->findAll('xpath', './/div[@class="sa_mc"]'); foreach ($elements as $element) { if (!preg_match($regex, $element->getText())) { throw new Exception('One of the elements did not match the searchterm'); } }}
PROFIELEN• Volledige configuratie per omgeving
• Selectie op basis van filters
• Te definieren in behat.yml:
• Aan te roepen met: $ bin/behat --profile profielnaam
default: extension: Behat\MinkExtension\Context\MinkContext base_url: “http://www.example.org”profielnaam: extension: Behat\MinkExtension\Context\MinkContext base_url: “http://acc.example.org”
TAGS• Handig om selectie te maken van features/scenario’s
• Via command line: $ bin/behat --tags “@slow”
• Definieer als filters in profile
@smokeFeature: Search on the www@slowScenario: ...
search.feature
slowonly: filters: tags: “@slow”
behat.ymlfast: filters: tags: “~@slow”
behat.yml
FILTERS• Tests groeperen• Snel vs. traag• Tags• Configuratie in behat.ymlsmoketests: filters: tags: “@smoketest&&~@wip”development: filters: tags: “~@slow&&~@wip”
HOOKS
• @beforeSuite
• @beforeFeature
• @beforeScenario
• @beforeStep
• @afterSuite
• @afterFeature
• @afterScenario
• @afterStep
HOOKS/** * @afterScenario */public function logoutUser(){ $this->visit('/logout');}
features/bootstrap/FeatureContext.php
BROWSER CONTROLLERS
Javascript Snelheid Opmerking
Goutte Nee ++ Emulator
Selenium2 Ja -
Sahi Ja ~Geen response
statuscode, headers, authenticatie
Zombie.js Ja +
SCENARIO OUTLINES Scenario Outline: Simple keyword search Given I am on the homepage When I search the term <searchterm> Then I should see search results containing <searchterm>
Examples: | searchterm | | PHP | | Java | | Pie | | This string is possibly too long and uncommon |
DRIVER BENCHMARK
Goutte 2.736ms
Selenium2 16.682ms
Sahi ???
Zombie.js 7.533ms
2007
0 5,000 10,000 15,000 20,000
Goutte Selenium Zombie.js
MULTILINE VARIABLES Scenario: Given I input that spans several lines """ Test one two three """
/** * @Given /^I input that spans several lines$/ */public function iInputThatSpansSeveralLines(PyStringNode $string){ (string) $string; $string->getRaw(); // string $string->getLines(); // array}
TABLES Scenario: Given the following users are registered | name | password | | Foo | test123 | | Admin | secret | When I go to the user overview Then I should see Foo And I should see Admin
/** * @Given /^the following users are registered$/ */public function theFollowingUsersAreRegistered(TableNode $table){ foreach ($table->getHash() as $row) { // ... $row['name'], $row['password'] }}
BEDANKT
• Feedback: http://joind.in/talk/view/8566
• Vragen?
• @Richard_Tuin
• skype: richardtuin