samedi, mars 31 2012

Intégration continue - PHP, Selenium et Xvfb - Partie 1

Depuis quelques mois je me suis penché sur les outils d'intégration continue pour les projets PHP. Il en existe un bon paquet, mais celui qui pour le moment répond le mieux à mes attentes reste phpUnderControl. L'installation reste tout de même assez complexe et il me parait bon de vous montrer comment mettre en oeuvre ce genre d'outil. Le but étant de vous aider pas à pas à installer les outils qui permettront d'utiliser un service d'intégration continue. Nous allons passer en revue les outils à installer depuis PEAR, puis Selenium et enfin comment intégrer tout ça dans phpUnderControl.

Certes, je ne vais pas vous donner "la solution dans les règles de l'art" car chaque projet est différent, chaque attente est spécifique... Mais vous pourrez adapter au besoin les explications.

Nous allons donc procéder à ces opérations:

  • installer les outils pear: phpcs, phpmd, phpcpd, phpunit
  • installer selenium IDE avec le plugin php (ces outils ne sont pas obligatoires mais rendent la tâche tellement plus simple...)
  • préparer un environnement X virtuel pour lancer les tests selenium
  • installer phpundercontrol
  • configurer la bête...

Les deux derniers points seront dans la partie 2, pour le moment il faut s'assurer d'avoir tous les composants nécessaires à la bonne conduite du projet.

En ce qui concerne Selenium et phpUnderControl, ce sont des outils développés en Java. On nous recommande d'utiliser le jre de oracle (comme ce n'est plus Sun...) mais pour ma part j'utilise openjdk sans aucun souci... alors pourquoi passer au propriétaire quand le libre fonctionne très bien ?

Donc, sur votre Fedora ou Centos, préparons un environnement propre... Je vous conseille d'utiliser un conteneur LXC pour bosser, mais si vous voulez utiliser votre système hôte je ne vais pas vous en empêcher.

On commence par installer les outils important:

  • php-cli, php-pear
  • openjdk

on ouvre un terminal, on se met en root et on installe tout ça

su -
yum install php-cli php-pear java-1.7.0-openjdk

Pour la suite, je sais que certains d'entres vous vont me taper sur les doigts, clamant haut et fort que les dépots Fedora ont les paquets PEAR demandés (ou pas...) mais je vais être très clair: les paquets sur les serveurs PEAR sont à jour, et certaines dépendances risquent de ne pas marcher si vous utiliser les RPM. C'est le cas aussi sur Debian (j'en ai fait les frais cette semaine). Donc ayez confiance et faites ce que je dis :)

On commence par faire deux ou trois manipulations sur PEAR pour être à l'aise (toujours en tant que root):

pear set-config auto_discover 1
pear upgrade pear
pear install --alldeps pear.phpunit.de/PHPUnit
pear install --alldeps phpunit/PHPUnit_Selenium
pear install --alldeps pear.phpunit.de/phpcpd
pear install --alldeps phpmd/PHP_PMD
pear install --alldeps PHP_CodeSniffer
pear install --alldeps channel://pear.phpdoc.org/PhpDocumentor-2.0.0a1

Si jamais il vous manque des paquets php, installez les, faites un "prear uninstall" du paquet qui a donné une erreur, et relancez l'installation du paquet.

Vous avez donc à présent:

  • phpunit pour faire des tests unitaires de vos développement php
  • phpunit-selenium qui est une classe permettant de piloter le serveur selenium qui lancera un navigateur pour tester des aspect fonctionnels
  • phpcpd (Copy Paste Detector) qui vérifie les "copier coller" de code
  • phpmd (Mess Detector) qui va vérifier des aspects complexe de code (variables non utilisé, code complexe, nom de variables ou fonctions...)
  • phpcs qui va vérifier si votre code est bien formaté selon des standards choisis
  • phpdocumentor qui permet de créer la documentation de vos projets

Cela étant fait, nous allons passer à la suite... Selenium !

Selenium est un ensemble d'outils qui permettent de piloter un navigateur au travers un serveur. Vous allez pouvoir vérifier si une page apparait correctement, si les liens sont bien présents sur une certaine page, et bien plus encore. Le principe est très simple:

  • on enregistre une séquence de manipulations et de tests
  • on l'exporte pour phpunit
  • on lance le test

Mais pour que cela fonctionne, il faut jouer un peu dans le terminal... rien de bien méchant mais important.

Dans votre terminal, en root, téléchargez le serveur:

mkdir /opt/selenium
cd /opt/selenium
wget http://selenium.googlecode.com/files/selenium-server-standalone-2.20.0.jar

Ce serveur, une fois lancé, pilotera un firefox tout seul. Sauf que voilà... si vous travaillez sur une machine de bureau alors aucun souci ne se profile... mais quand on travaille sur un serveur distant... sans écran... ça va aller mal !

La solution la plus simple, selon moi, est d'installer un firefox "standalone" sur /opt/selenium et de lancer tout ça dans un Xvfb (X virtuel). On va donc faire cela... toujours dans votre répertoire /opt/selenium:

wget "http://download.mozilla.org/?product=firefox-11.0&os=linux&lang=fr"
tar jxf firefox*.bz2

Si tout est ok, vous avez un répertoire "firefox" dans /opt/selenium

On va tenter de lancer firefox dans un X virtuel pour être certain que tout se passe bien:

Xvfb :99 &
DISPLAY=:99 /opt/selenium/firefox/firefox &
DISPLAY=:99 import -window root /tmp/snapshot.png

Maintenant, ouvrez /tmp/snapshot.png et vérifiez que vous voyez bel et bien un firefox ouvert. Si oui: nickel ! si non... heu vérifiez si il ne manque pas une librairie pour firefox (genre gdlib-dbus...)

Bref, si Xvfb et firefox ont marché alors on coupe:

killall Xvfb

Notez qu'on aurait put utiliser "xvb-run /opt/selenium/firefox/firefox" et utiliser le port retourné pour faire la capture, mais les lignes su-citées me paraissent plus explicites pour comprendre comment ça fonctionne.

Bref, utilisons maintenant le serveur selenium pour lancer un test.

Xvfb :99 &
sleep 2
PATH=/opt/selenium/firefox:$PATH DISPLAY=:99 java -jar selenium-server-standalone-2.20.0.jar &

Attendez un peu...il faut voir une ligne indiquant que le serveur tourne, ce genre de ligne:

INFO org.openqa.jetty.http.SocketListener - Started SocketListener on 0.0.0.0:4444
INFO org.openqa.jetty.util.Container - Started org.openqa.jetty.jetty.Server@1ff4689e

Cela peut prendre entre 5 et 30 secondes, soyez patient... A partir de là, le serveur écoute le port 4444, on va donc jouer un peu !

Créer un fichier test.php:

<?php
class Example extends PHPUnit_Extensions_SeleniumTestCase
{
  protected function setUp()
  {
    $this->setBrowser("*chrome");
    $this->setBrowserUrl("http://www.google.fr/");
  }
 
  public function testMyTestCase()
  {
    $this->open("/");
    $this->waitForPageToLoad("30000");
    //on vérifie que "Google" est sur la page...
    $this->verifyTextPresent("Google");
 
    //ce teste doit donner une erreur
    $this->verifyTextPresent("foo bar baz");
 
  }
}

Ce teste ne fait rien de compliqué... il ouvre Google et vérifie si le texte "Google" est sur la page...

Vous pouvez lancer le test:

phpunit test.php

Le résultat doit être:

PHPUnit 3.6.10 by Sebastian Bergmann.

F

Time: 5 seconds, Memory: 5.75Mb

There was 1 failure:

1) Example::testMyTestCase
foo bar baz
Failed asserting that false is true.


FAILURES!
Tests: 1, Assertions: 2, Failures: 1.

Cela indique donc que le serveur selenium a bien lancé firefox, la page google et vérifier qu'un texte "Google" apparait. En ce qui concerne "foo bar baz" cela donne une erreur. Donc:

  • 1 test
  • 2 assertions : texte "Google" et texte "foo bar baz"
  • 1 erreur, et une seule erreur !!! si vous en avez 2 alors c'est que cela n'a pas marché !

Voilà, on peut couper selenium et Xvfb:

killall Xvfb
kill $(ps ax | grep selenium | grep -v grep | awk '{print $1}')

Maintenant, on se crée un service pour lancer Xvfb et Selenium.

Avant tout, pour des raison pratiques, je vous conseille de ne *plus du tout* lancer Xvfb et Selenium en root, mais avec un utilisateur précis. Comme nous allons utiliser phpUnderControl qui est un plugin de cruisecontrol, et que ce dernier lancera son service avec un utilisateur recommandé, nous allons le créer de suite et l'utiliser pour lancer Xvfb et Selenium.

useradd -m -s /bin/bash cruisecontrol

Notez ici que je demande la création du répertoire personnel (via l'option -m) car firefox aura besoin de créer un répertoire de profil, de ce fait, /home/cruisecontrol va être créé.

Voilà, maintenant créons le service qui lance Xvfb et Selenium via cet utilisateur. Créer le fichier /etc/init.d/xselenium et déposez ce code:

#!/bin/bash
 
XLOCK="/var/lock/Xvfb.pid"
SLOCK="/var/lock/selenium.pid"
 
 
startXVFB(){
        [[ -f $XLOCK ]] && echo "Already Running" && return 1
        local PID
        PID=$(su cruisecontrol -c 'Xvfb :15 >/dev/null  2>&1 & echo $!')
        echo "Xvfb pid $PID"
        echo $PID > $XLOCK
 
}
 
startSelenium(){
        [[ -f $SLOCK ]] && echo "Selenium Already Running" && return 1
 
        local PID
        PID=$(su cruisecontrol -c 'PATH=/opt/selenium/firefox:$PATH DISPLAY=:15 java -jar /opt/selenium/selenium-server-standalone-2.20.0.jar >/dev/null 2>&1 & echo $!')
        echo "Selenium pid $PID"
        echo $PID > $SLOCK
}
 
case $1 in
        start)
                echo "Starting XVFB"
                startXVFB
                sleep 2
                echo "Starting Selenium server"
                startSelenium
                sleep 2
                ;;
 
        stop)
                kill $(cat $SLOCK)
                sleep 2
                kill $(cat $XLOCK)
                sleep 2
                rm -f $SLOCK $XLOCK
                ;;
       *)
               echo "$0 start|stop"
               ;;
esac

Rendez le executable:

chmod +x /etc/init.d/xselenium

Et lancez le service:

/etc/init.d.xselenium start

Vous devez donc avoir désormais un Xvfb et un selenium actif. Attendez un peu que le port 4444 soit ouvert pour commencer à lancer les tests, pour vérifier le port:

netstat -taupen | grep 4444

Si aucune ligne n'apparait, c'est que le service ne tourne pas.

Voilà pour la partie pear, selenium-server et Xvfb. Dans la partie 2 nous utiliserons Selenium IDE pour créer des tests, mais surtout nous installerons phpUnderControl afin de piloter notre intégration continue.

En espérant avoir donner quelques clefs...