Quantcast
Channel: Blog Prestaconcept » Symfony2
Viewing all articles
Browse latest Browse all 5

Procédure d’installation et configuration de Behat en français pour Symfony 2

$
0
0

Cet article a pour but de clarifier l’installation de behat. Il est fait par
et pour les développeurs débutants avec Behat et le
« Behavior-driven Development »
(BDD que l’on peut traduire par « développement conduit par les comportements »),
les curieux et les enthousiastes.

Symfony 2.2 et Behat 2.4 ont été utilisés; Aucun bundle n’a été blessé lors de la rédaction de cet article.

Mis à jour le 21/08/2013 pour symfony 2.3

Dépendances

Liste des dépendances via composer :


//fichier: composer.json
"require-dev": {
    ...
    "behat/behat": "2.4@stable",
    "behat/symfony2-extension": "*@stable",
    "behat/mink-bundle": "*@stable",
    "behat/mink-browserkit-driver": "*@stable",
    "behat/mink-zombie-driver": "*@stable"
}

Lancer l’installation :

composer.phar update

Un petit tour d’horizon :

  • « behat/behat » Le cœur de Behat, il ne sait rien faire tout seul.
  • « behat/gherkin » Parseur de scénario.
  • « behat/symfony2-extension » Intégration à Symfony 2, permet de générer et lancer des suites de tests
  • « behat/mink-extension » un navigateur internet « headless » se reposant sur des « drivers »; il ne sait rien faire tout seul.
  • « behat/mink-browserkit-driver » Un pilote qui effectue les tests (d’autres existent par ex. goutte, sahi etc.)
  • « behat/mink-zombie-driver » un autre pilote pour Mink qui permet de tester du javascript

Symfony2-extension

La documentation indique la configuration suivante qui est déjà en place sur symfony >= 2.2.

À la racine du projet, il est nécessaire d’ajouter le fichier de configuration « behat.yml » :

# fichier: behat.yml
default:
  extensions:
    Behat\Symfony2Extension\Extension:
      mink_driver: true
    Behat\MinkExtension\Extension:
      default_session: 'symfony2'
      base_url: http://acme.local/

La configuration de zombie.js est volontairement mise de coté pour l’instant.

L’extension symfony2 de Behat ne sait pas fonctionner correctement avec les sous-dossiers, par contre avec cette configuration, les pages symfony2 sont tout de même générées.

Mise en garde :
Seul la session « symfony2″ ne nécessite pas de serveur web.

C’est peut être suffisant pour des tests simples mais une validations des tests d’acceptation en bonne et due forme nécessite plus. Une solution consiste à lancer le serveur intégré à php >= 5.4 pour lancer les tests et simplement avec la commande app/console server:run.

Initialisation

bin/behat --init @AcmeDemoBundle
# ou dans le cas d'un bundle installé :
bin/behat --init vendors/acme/demo-bundle/Acme/DemoBundle/

Le dossier « Features » et le minimum utile sont créés dans le bundle.

Ajout de Features

Pour ajouter une feature, il suffit de créer un fichier par exemple « Acme/DemoBundle/Features/Demo.feature » avec la description de la fonctionnalité. Selon le contexte on peut l’écrire en anglais pour les projets open-source ou en français pour les projets client.

Par exemple :

Feature: greetings
  In order to salute
  As a user
  I need to see a greeting message

Scenario: An admin see a list of websites
  Given I am on homepage
  And I am a user
  When I click the demo link
  Then I should see

Examples:
  | message |
  | hello world |

Ou en français :

# language: fr
Fonctionnalité: Salutations
  Dans le but de saluer
  En tant qu'utilisateur
  J'ai besoin de voir un message de salutation

Scénario: Un utilisateur affiche un message de salutation
  Étant donné que je suis sur la page d'accueil
  Et que je suis un utilisateur
  Quand je clic sur le lien demo
  Alors je devrais voir

Exemples:
  | message |
  | bonjour tout le monde |

Pour décrire une fonctionnalité il faut d’abord définir 3 questions majeurs :

  1. Quoi ?
  2. Qui ?
  3. Comment ?

Dans notre exemple les 3 premières lignes répondent à ces questions. Elles ne sont pas traitées par Behat et ne ne servent qu’à comprendre le besoin. C’est la base du BDD.

Voici un autre exemple plus concret :

  1. Quoi : « Dans le but d’acheter un aspirateur »
  2. Qui : « En tant que visiteur »
  3. Comment : « Je dois de payer un aspirateur qui me convient »

Ensuite le scénario permet de décrire assez clairement comment se comporte l’application en indiquant les 3 principaux mots clés : Étant donné (Given) puis Quand (When) et pour finir Alors (Then). D’autres mots clés sont disponibles et visibles grâce à la doc ou aux commandes behat :

bin/behat --story-syntax --lang=fr
bin/behat -dl --lang=fr

Il est remarquable que le scénario ne contient pas d’informations au sujet de la présentation pourtant on aurait pu écrire : Alors je devrais voir "<h1 class="hello">bonjour tout le monde</h1>", ce serait une erreur car cette fonctionnalité pourrait aussi être vérifiable sur une autre plateforme tel qu’une application native iOS, seule l’implémentation change.

Plus de détails sur la structure des descriptions de fonctionnalités à voir dans la documentation : Gherkin markup.

Le contexte

Une fois que les features sont écrites, la ligne de commande va permettre de mettre en application les scénarios. D’abord de générer le code contextuel permettant d’exécuter les étapes puis ensuite de valider le fonctionnement de l’application en exécutant le scénario.

La commande suivante permet tout ça :

bin/behat @AcmeDemoBundle
# ou dans le cas d'un bundle installé :
bin/behat vendors/acme/demo-bundle/Acme/DemoBundle/

La base

À chaque fois qu’une étape est inconnue, Behat proposera d’ajouter le nécessaire par exemple :


/**
 * @Given /^I am logged in as "([^"]*)" with the password "([^"]*)"$/
 */
public function iAmLoggedInAsWithThePassword($arg1, $arg2)
{
    throw new PendingException();
}

On va donc rajouter cette méthode à notre context Acme/DemoBundle/Features/FeatureContext.php pour réaliser ce qui est demandé :


namespace Acme\DemoBundle\Features;

use Behat\Symfony2Extension\Context\KernelAwareInterface;
use Behat\MinkExtension\Context\MinkContext;

class FeatureContext extends MinkContext implements KernelAwareInterface
{
  //...

  /**
   * @Given /^I am logged in as "([^"]*)" with the password "([^"]*)"$/
   */
  public function iAmLoggedInAsWithThePassword($arg1, $arg2)
  {
    $this->visit('/admin/en/login');

    $this->fillField('username', $arg1);
    $this->fillField('password', $arg2);
    $this->pressButton('_submit');
  }
}

À noter que l’on utilise le context Mink qui fera office de client web.

Sub-Contexts

Il est possible de réutiliser de multiples Context sans avoir la limitation de l’héritage simple imposé jusqu’à php 5.3 par exemple je peux ajouter mes définitions dans UnixContext.php :

namespace Acme\DemoBundle\Features;

use Behat\Behat\Context\BehatContext;

class UnixContext extends BehatContext
{
    /**
     * @When /^I run "([^"]*)"$/
     */
    public function iRun($arg1)
    {
        throw new \PendingException();
    }
}

Et ensuite spécifier dans le Context principal les héritages à utiliser :

public function __construct(array $parameters)
{
    $this->useContext('unix', new \Acme\DemoBundle\Features\UnixContext($parameters));
}

À voir plus de détails sur le fonctionnement des Contexts

Des fixtures

On peut indiquer à doctrine ce que nécessite la feature par exemple :

Et il y a des aspirateurs:
    | robot aspirateur         |
    | aspirateur à manivelle   |

/**
 * @Given /^il y a des aspirateurs:$/
 */
public function ilYADesAspirateurs(TableNode $table)
{
    $em = $this->kernel->getContainer()->get('doctrine')->getEntityManager();
    foreach ($table->getRows() as $row) {
        $hoover = new Hoover();
        $hoover->setName($row[0]);
        $em->persist($hoover);
    }
    $em->flush();
}

Le test fonctionnel support de la validation du comportement

À chaque fois que la commande behat est lancée, on aura les indicateurs suivants permettant de vérifier la validité des fonctionnalités :

1 scénario (1 non définies)
5 étapes (1 succès, 3 ignorées, 1 non définies)
0m1.31s

Mink propose des méthodes d’assertions par exemple pour valider notre étape écrite plus tôt :

Alors je devrais voir <message>

On pourra rajouter au FeatureContext :

/**
 * @Given /^je devrais voir ([^"]*)$/
 */
public function jeDevraisVoir($arg1)
{
    $this->assertElementContainsText("h1", $arg1);
}

Test de Javascript grâce à Zombie.js

Zombie.js est un package de node. Pour travailler avec, on fournit du code javascript qui sera traiter par node.

Installation

Se référer à la documentation puis installer la version 1.4.1 qui est supporté par behat :


sudo npm install zombie@1.4.1 --global

Important, exporter le path en ajoutant ceci à votre .bashrc :


export NODE_PATH="/usr/lib/node_modules"

Utilisation de base

//file: assert_zombies_on_google.js
var Browser = require("zombie");
var assert = require("assert");

browser = new Browser();

//affiche un certain nombre d'éléments de debug ainsi que les requêtes effectuées :
browser.debug = true;

browser.visit("http://google.fr/").
  then(function() {
    assert.equal(browser.text("H1"), "Deferred zombies");
  }).
  fail(function(error) {
    console.log("Oops", error);
  });

On utilisera alors la commande node assert_zombies_on_google.js.

Le driver de Mink facilite l’utilisation de zombie.js dans une application php (behat).

Configuration pour Behat

Avant tout l’extension Behat pour zombie.js nécessite un serveur web comme indiqué plus haut.

Dans les features il faut utiliser l’annotation @javascript qui va indiquer à Mink que l’on a besoin de valider du javascript. Ensuite dans behat.yml on va spécifier quel « driver » utiliser.

default:
    extensions:
        Behat\MinkExtension\Extension:
            base_url: http://acme.local/
            zombie: ~
            javascript_session: zombie

Fonctionnement

Dès que behat est configuré avec zombie, Mink l’utilisera directement (par défaut c’est Goutte qui est utilisé). Donc toutes les méthodes de FeatureContext.php utiliseront zombie.js.

Par exemple lorsque l’on exécute $this->visit('/admin'); c’est la méthode Behat\Mink\Driver\ZombieDriver::visit() qui est lancée et exécute le code node.js suivant :


pointers = [];
browser.visit("{$url}", function(err) {
  if (err) {
    stream.end(JSON.stringify(err.stack));
  } else {
    stream.end();
  }
});

Utilisation avancée : valeur de retour

Si on veut aller plus loin suivant l’API de zombie.js, on peut tout de même utiliser la méthode evalJS :

$js = 'browser.visit("http://acme.local/");stream.end();';
$server = $this->getSession('zombie')
                ->getDriver()
                ->getServer();
$out = $server->evalJS($js, 'js');

Il existe deux types de valeur de retour : js ou json.

Les deux exemples suivants sont équivalents et affichent par exemple « int(200) » :


var_dump((int) $server->evalJS('stream.end(JSON.stringify(browser.statusCode))'));
var_dump($server->evalJS('browser.statusCode', 'json'));

Utilisation avancée : ajax

Comme sur tous les navigateurs, il faut attendre l’exécution des différentes requêtes : On va indiquer un temps d’attente maximum et une condition :


use Behat\MinkExtension\Context\MinkContext

class PageContext extends MinkContext
{
    /**
     * @Given /^I should see a tree of pages$/
     */
    public function iShouldSeeATreeOfPages()
    {
        $this->getSession()->wait(2, '(window.jQuery("#tree ul").length > 0)');
        $this->assertNumElements(2, "#tree ul li");
    }
}

On notera dans cet exemple la variable « window » rendu disponible grâce à zombie ainsi que l’exécution de javascript (on aurait pu écrire window.$("#tree ul").length).

Utilisation avancée : assertion complexe

Dans certains cas le contexte Mink ne suffit plus et on veut par exemple combiner zombie.js avec assert de nodejs :


use Behat\MinkExtension\Context\MinkContext
use Behat\Mink\Exception\ExpectationException;

class PageContext extends MinkContext
{
    /**
     * @Given /^I should see a tree of pages$/
     */
    public function iShouldSeeATreeOfPages()
    {
        $js = <<<JS
                var assert = require("assert");

                try {
                    assert.equal(browser.text("h1"), "Pages");
                    browser.clickLink("Navigation");
                    assert.equal(browser.text("#page-tree-container h4"), "Navigation");
                } catch(err) {
                    stream.end(JSON.stringify(err.toString()));
                }

                stream.end();
JS;

        $out = $this->getSession('zombie')->getDriver()->getServer()->evalJs($js);

        if (!empty($out)) {
            throw new ExpectationException(json_decode($out), $this->getSession('zombie'));
        }
    }
}

Intégration à Jenkins

Le cas particulier de test de bundles dans les vendors nécessite de spécifier le chemin pour chaque bundle.

Dans tous les cas il configurer Ant pour qu’il lance la validation :

<target name="behat" description="Validates features">
    <exec executable="bin/behat" failonerror="true">
        <arg value="--format=junit" />
        <arg value="--out=${basedir}/build/junit" />
        <arg path="${basedir}/vendor/acme/demo-bundle/Acme/DemoBundle/" />
    </exec>
</target>

Ensuite il est nécessaire de vérifier la configuration du job sur jenkins :
« Publier le rapport des résultats des tests JUnit » doit contenir la valeur build/junit/*.xml pour permettre à jenkins d’afficher le log d’erreur pour phpunit et behat. »

Astuces

Voir le contenu de la page web

Dans le Context, écrire le contenu directement dans un fichier :

protected function logLastResponse()
{
    file_put_contents('behat.out.html', $this->getSession()->getPage()->getContent());
}

Intégration au Makefile

Pour simplifier l’execution de behat on peut ajouter les commandes au Makefile :

test-all: test-install test-behat test-clean
test-install:
    #installation de la base de test et des fixtures
test-behat:
bin/behat vendor/presta/cms-core-bundle/Presta/CMSCoreBundle/
test-clean:
    #suppression de la base ou des fichiers générés

Chargement de fixtures

Par défaut aucune fixture n’est gérée par behat et comme vu dans l’astuce précédente le chargement des fixtures peut être géré un seule fois dans le Makefile.

L’extension Doctrine data fixtures permet de gérer ce chargement avec l’isolement nécessaire pour chaque validation de Feature.

Liens et références


Viewing all articles
Browse latest Browse all 5

Latest Images

Trending Articles





Latest Images