Drupal camp paris 2013: présentation de Behat, Mink et Drupal extension

Preview:

DESCRIPTION

Introduction aux tests de recette automatisés avec l'extension Drupal pour Behat

Citation preview

Introduction aux tests de recette automatisésAvec l'extension Drupal pour Behat

didier.boff@ows.fr

B2F @ drupal.orghttps://github.com/B2Fhttp:///twitter.com/zenelse

Les joies du testQuand mon collègue avance sans plan de test

http://lesjoiesdutest.tumblr.com

Plan

● Retour d'expérience○ Campus France○ Divers outils démodés

Plan

● Retour d'expérience○ Campus France○ Divers outils démodés

● Méthodologie○ BDD, Gherkin

Plan

● Retour d'expérience○ Campus France○ Divers outils démodés

● Méthodologie○ BDD, Gherkin

● Développement○ Behat/Mink○ MinkExtension (démo)○ DrupalExtension (démo)

Retour d'expérience

www.campusfrance.org

● Migration Drupal 6 -> 7

Retour d'expérience

www.campusfrance.org

● Migration Drupal 6 -> 7● 100 sites, domain access● multilingues

Retour d'expérience

www.campusfrance.org

● Migration Drupal 6 -> 7● 100 sites, domain access● multilingues

Quels tests ?

Retour d'expérience

SeleniumAPI

● click● type● select● addSelection● submit● ... http://release.seleniumhq.org/selenium-

core/

Retour d'expérience

SeleniumIDE

Retour d'expérience

?Test automatisé

SeleniumIDE

Retour d'expérience

SeleniumIDE

?Test automatisé

Multidomaine

Retour d'expérience

SeleniumIDE

?AJAXMultidomaine

Test automatisé

Retour d'expérience

Selenium Server

http://docs.seleniumhq.org/download/

java -jar selenium-server-standalone-<version-number>.jar

http://selenium.googlecode.com/files/selenium-server-standalone-2.33.0.jar

Retour d'expérience

Selenium Server+ PHPUnit

PHPUnit_Extensions_SeleniumTestCase

Retour d'expérience

Selenium Server+ PHPUnit

https://github.com/B2F/ows-phpunitclass OWSeleniumTestCase extends PHPUnit_Extensions_SeleniumTestCase {

public function __construct($name = NULL, array $data = array(), $dataName = '') {

Retour d'expérience

Selenium Server + PHPUnit + IDE

Retour d'expérience

Selenium Server + PHPUnit + IDE +Selenium IDE: PHP Formattershttps://github.com/brokenthumbs/PHP-Formatter

Retour d'expérience

Selenium Server + PHPUnit + IDE +Selenium IDE: PHP Formattershttps://github.com/brokenthumbs/PHP-Formatter

https://github.com/B2F/PHP-Formatter

Les joies du testQuand je fais un test un peu trop compliqué pour un truc tout simple

http://lesjoiesdutest.tumblr.com

Les joies du test

Quand je fais un test un peu trop compliqué pour un truc tout simple ?

Les joies du test

Quand je fais un test un peu trop compliqué pour un truc tout simple

Les joies du test

Quand je fais un test un peu trop compliqué pour un truc tout simple

19 content types

Les joies du test

Quand je fais un test un peu trop compliqué pour un truc tout simple

...

Méthodologie

● Behavior Driven Development (BDD)

Méthodologie

● Behavior Driven Development (BDD)DrupalCon Portland 2013: BEHAT, BEHAVIORAL-DRIVEN DEVELOPMENT AND SELENIUM IN DRUPALRyan Weaver

https://portland2013.drupal.org

Méthodologie

● Gherkin

Méthodologie

● Gherkin

● Feature Suite de tests

Méthodologie

● Gherkin

● Feature○ Scenario Test

Méthodologie

● Gherkin

● Feature○ Scenario

■ Given● And...

Contexte

Méthodologie

● Gherkin

● Feature○ Scenario

■ Given● And...

Contexte

Given I am on "/restaurants"

Méthodologie

● Gherkin

● Feature○ Scenario

■ Given● And...

■ When● And... Évènements

Méthodologie

● Gherkin

● Feature○ Scenario

■ Given● And...

■ When● And... Évènements

And I enter "Vietnam" for "pays" And I enter "Bo Bun" for "plats" And I enter "Paris 13" for "lieu"

Méthodologie

● Gherkin

● Feature○ Scenario

■ Given● And...

■ When● And...

■ Then● And...

Résultats

Then I should see the text "Pho Bida"

Opened Blackbox

● Cucumber

Gherkin + Ruby

Opened Blackbox

● Cucumber

=

Gherkin + Ruby

Opened Blackbox

● Cucumber

Gherkin + Ruby ?

Opened Blackbox

● Cucumber

Gherkin + PHP

Opened Blackbox

● Behat

http://behat.org/

=

Gherkin + PHP

Opened Blackbox

http://behat.org/

<?php

use Behat\Behat\Context\BehatContext

class FeatureContext extends BehatContext{// /**// * @Given /^I have done something with "([^"]*)"$/// */// public function iHaveDoneSomethingWith($argument)// {// doSomethingWith($argument);// }}

● Behat: ça fait quoi ?

Opened Blackbox

http://behat.org/

<?php

use Behat\Behat\Context\BehatContext

class FeatureContext extends BehatContext{// /**// * @Given /^I have done something with "([^"]*)"$/// */// public function iHaveDoneSomethingWith($argument)// {// doSomethingWith($argument);// }}

● Behat: ça fait quoi ?

Opened Blackbox

● Mink

Web acceptance testing

http://mink.behat.org/

Opened Blackbox

● Mink: drivers

$driver = new \Behat\Mink\Driver\ZombieDriver();

http://mink.behat.org/

Opened Blackbox

● Mink: drivers

$driver = new \Behat\Mink\Driver\SahiDriver('firefox');

http://mink.behat.org/

Opened Blackbox

● Mink: drivers

$driver = new \Behat\Mink\Driver\GoutteDriver();

http://mink.behat.org/

Opened Blackbox

● Mink: drivers

$client = new \Selenium\Client($host, $port);$driver = new \Behat\Mink\Driver\SeleniumDriver( 'firefox', 'base_url', $client);

http://mink.behat.org/

Opened Blackbox

● Mink: session

// init session:$session = new \Behat\Mink\Session($driver);

// start session:$session->start();

http://mink.behat.org/

$session->visit('http://paris2013.drupalcamp.fr');

Opened Blackbox

● Mink: méthodes

$page = $session->getPage();$element = $page->find('css', 'a#selector');$element->getText();

http://mink.behat.org/

Opened Blackbox

● Mink: méthodes

$page = $session->getPage();$element = $page->find('css', 'a#selector');$element->getText();

http://mink.behat.org/

Développement

● En pratique

http://mink.behat.org/

Behat + Mink ?

Développement

● Mink extension

http://mink.behat.org/

=Behat + Mink

Développement

● Mink extension

http://mink.behat.org/

STEPS

Développement

● Mink extension

http://mink.behat.org/

Given /^(?:|I )am on (?:|the )homepage$/ When /^(?:|I )go to (?:|the )homepage$/Given /^(?:|I )am on "(?P<page>[^"]+)"$/ When /^(?:|I )go to "(?P<page>[^"]+)"$/ When /^(?:|I )reload the page$/ When /^(?:|I )move backward one page$/ When /^(?:|I )move forward one page$/ When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/ When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/ When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/ When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with:$/ When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)"$/ When /^(?:|I )fill in the following:$/ When /^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/ When /^(?:|I )additionally select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/...

bin/behat -dl

Développement

● Mink extension

http://mink.behat.org/

Presses button with specified id|name|title|alt|value.@When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/

Clicks link with specified id|title|alt|text.@When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/

Behat/MinkExtension/Context/MinkContext.php

Développement

Installation

http://docs.behat.org/#cookbook

Developing Web Applications with Behat and Mink

Développement

Installationcomposer.json{ "require": { "behat/behat": "2.4.*@stable", "behat/mink": "1.4.*@stable", "behat/mink-extension": "*", "behat/mink-goutte-driver": "*", "behat/mink-selenium2-driver": "*" }, "minimum-stability": "dev", "config": { "bin-dir": "bin/" }} http://docs.behat.org/cookbook/behat_and_mink.html

Développement

Installation

$ curl http://getcomposer.org/installer | php$ php composer.phar install

http://docs.behat.org/cookbook/behat_and_mink.html

Développement

Installation

$ lsbin composer.json composer.lock composer.phar vendor

http://docs.behat.org/cookbook/behat_and_mink.html

Développement

Installation

$ bin/behat --init

-> features/

Développement

Installation: suites de tests

features/{name}.feature

Feature: un exemple Quelques steps pour illustrer une feature

Scenario: vérification qu'un lien est présent Given I am at "/home" When I click "Se connecter" Then I should see "login"

Développement

Installation: customisation

$ bin/behat --init

-> features/bootstrap/FeatureContext.php

Développement

Installation: customisation

# features/bootstrap/FeatureContext.php

/** * Features context. */class FeatureContext extends BehatContext{...

Développement

Installation: customisation

# features/bootstrap/FeatureContext.php

/** * Features context. */class FeatureContext extends BehatContext{...

Développement

Installation: customisation

# features/bootstrap/FeatureContext.php

/** * Features context. */class FeatureContext extends MinkContext{...

Développement

Installation: subcontexts

# features/bootstrap/FeatureContext.php

use Behat\MinkExtension\Context\RawMinkContext;use Behat\MinkExtension\Context\MinkContext;

class FeatureContext extends RawMinkContext{ public function __construct(array $parameters) { $this->useContext('mink', new MinkContext); }}

http://extensions.behat.org/mink/

Développement

Installation: subcontexts

# features/bootstrap/FeatureContext.php

use Behat\MinkExtension\Context\RawMinkContext;use Behat\MinkExtension\Context\MinkContext;

class FeatureContext extends RawMinkContext{ public function __construct(array $parameters) { $this->useContext('mink', new MinkContext); }}

http://extensions.behat.org/mink/

Développement

● Mink steps

When will this be over ?

http://mink.behat.org/

Développement

● Mink steps

When I hover the element ?

/*** @When /^I hover "([^"]*)"$/*/public function iHover($cssId){

http://mink.behat.org/

When will this be over ?

Développement

● Mink steps

/*** @When /^I hover "([^"]*)"$/*/public function iHover($cssId){$page = $this->getSession()->getPage();$element = $page->find('css', $cssId);

http://mink.behat.org/api/behat/mink/element/nodeelement.html

Développement

● Mink steps

public function iHover($cssId){ $page = $this->getSession()->getPage(); $element = $page->find('css', $cssId); if (null === $element) { throw new ElementNotFoundException($this->getSession(), 'element', 'css', $cssId); }

http://mink.behat.org/api/behat/mink/element/nodeelement.html

Développement

● Mink steps

__construct(Session session, string type, string selector, string locator) And I hover "a[title='abcdefghi']" # FeatureContext::iHover() Element matching css "a[title='abcdefghi']" not found.

throw new ElementNotFoundException($this->getSession(), 'element', 'css', $cssId);

http://mink.behat.org/api/behat/mink/element/nodeelement.html

Développement

● Mink steps

public function iHover($cssId){ $page = $this->getSession()->getPage(); $element = $page->find('css', $cssId); if (null === $element) { throw new ElementNotFoundException($this->getSession(), 'element', 'css', $cssId); } $element->mouseOver();

http://mink.behat.org/api/behat/mink/element/nodeelement.html

Développement

● Mink steps

public function iHover($cssId){ $page = $this->getSession()->getPage(); $element = $page->find('css', $cssId); if (null === $element) { throw new ElementNotFoundException($this->getSession(), 'element', 'css', $cssId); } $element->mouseOver();

Attention: Selenium supporte le mouseOver Javascript only :(

http://mink.behat.org/api/behat/mink/element/nodeelement.html

Développement

● Mink steps - AJAX

/** * Waits some time or until JS condition turns true. * * @param integer $time time in milliseconds * @param string $condition JS condition */ public function wait($time, $condition = 'false') { $this->driver->wait($time, $condition); }

http://mink.behat.org/api/source/behat/mink/session.php.html

Développement

● Mink steps - AJAX

/** * @When /^I wait (?P<timing>\d+)sec$/ */ public function iWaitNSec($timing) { $this->getSession()->wait($timing*1000); }

http://mink.behat.org/api/source/behat/mink/session.php.html

Développement

● Mink steps - AJAX

/** * @When /^I wait (?P<timing>\d+)sec$/ */ public function iWaitNSec($timing) { $this->getSession()->wait($timing*1000); }

http://mink.behat.org/api/source/behat/mink/session.php.html

Développement

Démo

bin/behat features/demo.feature

Développement

Démo: colored output

bin/behat --ansi features/demo.feature

behat arguments: http://docs.behat.org/guides/6.cli.html

Développement

● OwsContext

https://github.com/B2F/OwsContext

public function iHover($cssId)public function iWaitNsec($timing)public function iClickTheElementMatching($selector)

Développement

● OwsContext

https://github.com/B2F/OwsContext+ (experimental)

public function iWaitNSecForTheText($timing, $text)public function iSwitchToTheIframeNamed($iframe)public function iSwitchBackFromIframe()

Développement

Configuration

# behat.ymldefault: extensions: Behat\MinkExtension\Extension: base_url: http://google.fr goutte: ~ selenium2: ~

Développement

Configuration● AJAX

@javascript Scenario: vérification qu'un lien est présent Given I am at "/home" When I follow "my account" Then I should see "my page title"

Développement

Configuration● Profiles = overrides

# behat.ymldefault:...custom: extensions: Behat\MinkExtension\Extension: base_url: http://another-url.com

Développement

Configuration● Profiles

# bin/behat --profile=custom features/tests.feature

Développement

Configuration● Filtres

# behat.ymlcustom:... extensions: ... filters: tags: "@custom-filter"

Développement

Configuration● Filtres

@javascript @custom-filter Scenario: vérification qu'un lien est présent Given I am at "/home" When I hover "Se connecter" Then I wait 1sec for the text "login"

Développement

● Drupal Extension

https://drupal.org/project/drupalextension

Développement

● Drupal Extension/** * Features context. */class DrupalContext extends MinkContext implements DrupalAwareInterface {

private $drupal, $drupalParameters;

/** * Basic auth user and password. */ public $basic_auth;...

https://drupal.org/project/drupalextension

Développement

● Drupal Extension: installation # composer.json{ "require": {

"drupal/drupal-extension": "*" }, "minimum-stability": "dev", "config": {

"bin-dir": "bin/" }}

https://drupal.org/project/drupalextension

Développement

● Drupal Extension: installtion

$ curl http://getcomposer.org/installer | php$ php composer.phar install

https://drupal.org/project/drupalextension

Développement

● Drupal Extension: configuration#behat.ymldefault: paths:

features: 'features' extensions:

Behat\MinkExtension\Extension: goutte: ~ selenium2: ~ base_url: http://www.campusfrance.org/

https://drupal.org/project/drupalextension

Développement

● Drupal Extension: configuration#behat.yml

default: paths:

features: 'features' extensions:

Behat\MinkExtension\Extension: goutte: ~ selenium2: ~ base_url: http://www.campusfrance.org/

Drupal\DrupalExtension\Extension: blackbox: ~

https://drupal.org/project/drupalextension

Développement

● Drupal Extension en pratique

bin/behat -dl => liste les steps

https://drupal.org/project/drupalextension

Développement

● Drupal Extension: user steps

Given /^I am an anonymous user$/Given /^I am not logged in$/Given /^I am logged in as a user with the "(?P<role>[^"]*)" role$/

https://drupal.org/project/drupalextension

Développement

● Drupal Extension: configuration#behat.yml

default: paths:

features: 'features' extensions:

...Drupal\DrupalExtension\Extension:blackbox: ~

drush: alias: myDrushAlias

https://drupal.org/project/drupalextension

Opened blackbox

● Drupal Extension: user steps

# @see Drupal/DrupalExtension/Context/DrupalContext.php /** * Helper function to login the current user. */ public function login() {...

$this->getSession()->visit($this->locatePath('/user'));$element = $this->getSession()->getPage();$element->fillField($this->getDrupalText('username_field'), $this->user->name);$element->fillField($this->getDrupalText('password_field'), $this->user->pass);$submit = $element->findButton($this->getDrupalText('log_in'));

https://drupal.org/project/drupalextension

Développement

● Drupal Extension: user steps

#behat.yml

Drupal\DrupalExtension\Extension: text: log_out: "Sign out" log_in: "Sign in" password_field: "Enter your password" username_field: "Nickname"

https://drupal.org/project/drupalextension

Développement

● Drupal Extension: content steps

Given /^I am viewing (?:a|an) "(?P<type>[^"]*)" node with the title "(?P<title>[^"]*)"$/Given /^I am viewing (?:a|an) "(?P<vocabulary>[^"]*)" term with the name "(?P<name>[^"]*)"$/

https://drupal.org/project/drupalextension

Développement

● Drupal Extension: region steps

Then /^I should see the "(?P<heading>[^"]*)" heading in the "(?P<region>[^"]*)"(?:|region)$/When /^I (?:follow|click) "(?P<link>[^"]*)" in the "(?P<region>[^"]*)"(?:| region)$/Then /^I should see the link "(?P<link>[^"]*)" in the "(?P<region>[^"]*)"(?:| region)$/Given /^I press "(?P<button>[^"]*)" in the "(?P<region>[^"]*)"(?:| region)$/

https://drupal.org/project/drupalextension

Développement

● Drupal Extension: region steps

#behat.yml

Drupal\DrupalExtension\Extension: region_map:

My region: "#css-selector"Content: "#main .region-content"Right sidebar: "#sidebar-second"

https://drupal.org/project/drupalextension

Développement

● Drupal Extension: misc steps

Given /^the cache has been cleared$/Given /^I run cron$/

https://drupal.org/project/drupalextension

Développement

Démo

Développement

Conclusion:

Utilisez BDD pour faire des tests de régressions.

Introduction aux tests de recette automatisésAvec l'extension Drupal pour Behat

didier.boff@ows.fr

B2F @ drupal.orghttps://github.com/B2Fhttp:///twitter.com/zenelse

Merci de votre attention