29/03/2012

Behat - jour 4 : API Mink, Sous-contextes et Hooks

Voici l'avant dernier billet de la série sur la prise en main de Behat. Pour rappel, on a vu :

Allons un peu plus loin :-)

Sortir des sentiers battus : l'API de Mink

Assez rapidement on se retrouve à devoir gérer des cas particuliers, par exemple :

Quand je suis à découvert
<p>Alors le bouton "retirer de l'argent" doit être désactivé

On sort des cas classiques de Mink. Comment faire ?

La solution consiste à développer nous même ce comportement :

/**
 * @Then /^le bouton "([^"]*)" doit être désactivé$/
 */
public function leBoutonDoitEtreDesactive($button)
{
    throw new PendingException();
}

On commence par récupérer notre page à partir de l'objet de session:

$page = $this->getSession()->getPage();

Ensuite on va récupérer l'élément html concerné. Il existe différente manière de faire cela. Le plus simple dans notre cas consiste à passer par un des raccourcis de sélection de Mink : findButton(libellé | id | nom)...

$element = $page->findButton($button);

Si l'élément html n'est pas trouvé, on va lever une exception, sinon on va continuer en faisant une assertion simple : l'élément doit avoir l'attribut "disabled". Ce qui donne au final :

$page = $this->getSession()->getPage();
$element = $page->findButton($button);

if (null === $element) {
    throw new Behat\Mink\Exception\ElementNotFoundException(
        $this->getSession(), 'element', 'css', $button
    );
}

<p>assertEquals(true, $element->hasAttribute('disabled'));

Au passage, remarquez qu'il s'agit d'une assertion classique de PHPUnit, mais en mode fonction. Pour cela on aura bien entendu ajouté au début de notre fichier :

require_once 'PHPUnit/Autoload.php';
<p>require_once 'PHPUnit/Framework/Assert/Functions.php';

Je vous laisse regarder la documentation ou la feuille d'astuce pour Mink pour plus d'informations. Sachez juste qu'on peut faire pas mal de chose, comme exécuter du JavaScript par exemple (avec $session->evaluateScript() ) ;-) ...

Organiser son code

Jusqu'ici on a systématiquement mis notre code dans le fichier FeatureContext.php. C'est pas l'idéal : on va très vite se retrouver avec un fichier énorme et imbuvable. Il nous suffit de découper notre contexte en sous-contextes. Tout se fait dans le constructeur du contexte principal :

class FeatureContext extends BehatContext
{

    public function __construct(array $parameters) {
        $this->useContext('mink', new MinkContext($parameters));
        $this->useContext('example1', new MyExample1Context($parameters));
    }

On a donc isolé le contexte de Mink pour en faire un sous-contexte, et on a plus ajouté le nôtre ('example1').

L'utilisation des contextes est assez simple. Chaque sous-contexte a un nom (ici 'mink' et 'behat'), que l'on peut utiliser pour les récupérer :

getMainContext()
Récupérer le contexte principal
getSubContext('nom')
Récupérer un sous contexte
getSubcontexts()
Récupérer la liste des tous les sous-contextes

Par exemple :

$session = $this->getMainContext()->getSubContext('mink')->getSession();

On va donc créer le fichier MyExample1Context.php :

class MyExample1Context extends BehatContext
{

    /**
     * @Then /^le bouton "([^"]*)" doit être désactivé$/
     */
    public function leBoutonDoitEtreDesactive($button) {
        $session = $this->getMainContext()->getSubContext('mink')->getSession();
        $page = $session->getPage();
        $element = $page->findButton($button);

        if (null === $element) {
            throw new Behat\Mink\Exception\ElementNotFoundException(
                    $this->getSession(), 'element', 'css', $button
            );
        }

        assertEquals(true, $element->hasAttribute('disabled'));
    }

}

Et voilà, nous voici avec un code découpé et des fichiers plus spécialisés.

Les hooks de Behat

Comme pour les tests unitaires, il est possible d'exécuter du code à certaines phases du déroulement du tests. Il suffit d'utiliser des annotations :

/**
 * @BeforeSuite
 */
<p>public static function prepare(SuiteEvent $event)</p>
{
    // (...)
}

les déclencheurs disponibles sont :

  • BeforeSuite
  • AfterSuite
  • BeforeFeature
  • AfterFeature
  • BeforeScenario
  • AfterScenario
  • BeforeStep
  • AfterStep
  • AfterStep

Un dessin valant mieux qu'un long discours, le plus simple est de regarder ici.

Conclusion

On voit qu'on a quand même peu de limites avec Mink et Behat. Bien plus, on peut même l'intégrer à une PIC (Jenkins, Hudson...). C'est d'ailleurs ce qu'on verra dans le prochain billet, qui sera le dernier de la série ^^

=> Juste par curiosité : j'utilise exclusivement Sahi pour mes tests. Beaucoup de monde utilise Selenium ? C'est mieux ? Vous avez des avis ?
blog comments powered by Disqus