Les tests unitaires

Dans le billet précédent sur les tests en théorie, on a détaillé les différents types de tests dont les tests unitaires. On peut tenter d'appliquer cette démarche au développement web/PHP dans sa maturité actuelle.

Dans ce domaine PHP-Unit est une référence, il permet des jeux de tests pour une fonction et d'en vérifier les résultats.

Comme indiqué précédemment, l'utilité des outils de tests unitaires dans un développement php me semble limité.

Quelles fonctions sont testables unitairement dans une application web MVC ? (voir le design pattern MVC) Le DAO fera toujours un appel à la base de donnée et le contrôleur au dao... Ce n'est donc plus un test unitaire. Ceci ne remet pas en cause l'utilité de tester, mais remet en cause la façon de le faire.

Tests automatisés : non-regression

Il semble plus judicieux d'exploiter php-unit pour écrire des tests automatisés. Le triplet SeleniumIDE, Selenium Remote Controle et php-unit permet des choses particulièrement intéressantes en automatisant la génération de test. Le plug-in firefox de Selenium IDE permet d'enregistrer un scénario
Exemple :
J'enregistre que je charge une page, que je clique sur un lien, que je saisie des champs, que j'appuie sur un bouton.

Enregistrement d'une session firefox en cours...


Cet enregistrement peut être exporté en PHP. Le code généré :

<?php

require_once 'PHPUnit/Extensions/SeleniumTestCase.php';

class Example extends PHPUnit_Extensions_SeleniumTestCase
{
  function setUp()
  {
    $this->setBrowser("*chrome");
    $this->setBrowserUrl("http://blog.nalis.fr/");
  }

  function testMyTestCase()
  {
    $this->open("/");
    $this->click("link=Formulaire de contact");
    $this->waitForPageToLoad("30000");
    $this->type("c_name", "test");
    $this->type("c_subject", "test");
  }
}
?>


Ensuite, il convient de compléter ce code pour en faire un véritable test. Il faut variabiliser les données, puis ajouter des assertions. Les assertions seront vérifiées pour chaque jeu de données et le scénario sera répété jusqu'à épuisement des jeux de données.
De cette manière on peut rejouer ce test avec des jeux de données différents. C'est extrêmement utile pour rejouer de manière automatiser des tests de non-régression!
Ce dispositif doit être accompagné d'une démarche pro-active. Chaque bug corrigé ne devrait être fermé qu'après avoir ajouté une assertion vérifiant automatiquement la non reproductibilité de l'anomalie.

Par l'utilisation de Selenium, on a généré du code source sans l'écrire. Ce qui fait de ce logiciel un bon outil de génie logiciel et s'inscrit pleinement dans la démarche d'atelier de génie logiciel déjà plongement décrite précédemment (voir les types d'outil de génie logiciel et la constitution d'un AGL PHP). Cette démarche de test s'inscrit dans le cadre d'un projet s'incrivant dans un cycle en V (mais on peut aussi l'imaginer avec un projet en mode SCRUM).

Compatibilité

On traite ici d'un problème spécifique au interface web : vérifier le rendu des pages sur les navigateurs du marché.

Différents outils proposent de tester plusieurs versions d'Internet Explorer : Multiple IE ou IETester.
Mais utiliser des machines virtuelles pour tester la compatibilité des navigateurs reste la seule solution fiable. Pour un même navigateur, les rendus peuvent être différent en fonction de l'os (notamment IE).

Les principales plateformes de virtualisation sont : Virtual PC, Virtual Box, Vmware. On trouve sur le net des images de systèmes Mac. On peut donc sur une même machine partagée (par exemple avec vmware server) disposer d'environnements Windows IE7, Windows IE8, Mac Safari, etc ...
Il existe des services en ligne générant le rendu d'une url pour un navigateur donné. Mais cela suppose que le développement à tester soit exposé sur internet.

Parseurs de norme

Marre de tomber sur des fonctions de plus de 100 lignes ? Marre de voir des "SELECT *" ? ...?
Le parseur de norme est pour vous.
Dans le domaine, PHPcodesniffer est le principal outil. PHPcodesniffer est un paquet pear qui s'exécute en ligne de commande. PHPcodesniffer inclus déjà plusieurs standard de codage dont celui de Zend. On peut personaliser les règles pour ajouter les siennes.
Par exemple pour signaler les select * :

function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
    {
         $tokens = $phpcsFile->getTokens();
         if (preg_match("/select .*\*/i",$tokens[$stackPtr]["content"]))
         {
             $error = "\" select *\" is prohibed, found : ".$tokens[$stackPtr]["content"];
             $phpcsFile->addError($error, $stackPtr);
         }
    }


Résultat:
Les développeurs sont particulièrement sensibles à ce genre de tests. L'évolution à la baisse du nombre d'erreurs est généralement la justification d'une certaine fierté (ou à l'inverse d'une démotivation généralisée quand le nombres d'erreurs augmente). En comparaison, Obtenir des commentaires adaptés est généralement beaucoup plus difficile.

Test de charge

On ne devrait jamais mettre une application en ligne sans réaliser un test de charge. Il n'existe pas d'outil spécifique au php. Le test de charge fait abstraction de la technologie employé par le serveur, il se contente d'analyser le temps de réponse à chaque requête. Dans ce domaine on retrouve principalement 2 outils :
  • ab de la fondation Apache
  • httperf de HP
Il n'y a plus particulièrement d'innovation dans ce domaine, le besoin étant satisfait par ces outils. Ces outils évoluent peu (plus).
Exemple d'utilisation d'httperf :
httperf --server=www.site.fr --port=80 --num-conns=20 --rate=10 -v --timeout 5 --wlog=n,requests_site.txt
Dans cet exemple on utilise 20 connexions pour réaliser 10 requête par seconde sur le domaine site.fr. Les url utilisées pour le test sont piochée dans le fichier requests_site.txt (ça boucle si besoin). Ce qui donne le résultat :

Maximum connect burst length: 1

Total: connections 10 requests 10 replies 0 test-duration 55.108 s

Connection rate: 0.2 conn/s (5510.8 ms/conn, <=10 concurrent connections)
Connection time [ms]: min 0.0 avg 0.0 max 0.0 median 0.0 stddev 0.0
Connection time [ms]: connect 83.2
Connection length [replies/conn]: 0.000

Request rate: 0.2 req/s (5510.8 ms/req)
Request size [B]: 67.0

Reply rate [replies/s]: min 0.0 avg 0.0 max 0.0 stddev 0.0 (11 samples)
Reply time [ms]: response 0.0 transfer 0.0
Reply size [B]: header 0.0 content 0.0 footer 0.0 (total 0.0)
Reply status: 1xx=0 2xx=0 3xx=0 4xx=0 5xx=0

CPU
time [s]: user 4.20 system 33.92 (user 7.6% system 61.6% total 69.2%)

Net I/O: 0.0 KB/s (0.0*10^6 bps)

Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0

Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

Le Request rate est de 0,2 req/seconde. C'est donc un exemple particulièrement catastrophique.

Profiler, mesure de la performance

Après un test de charge comme le précédent, le profiling permet d'identifier quelles ressources sont utilisées pour réaliser un traitement. Si un écran particulier est long à charger. Le profilling va permettre d'identifier le ou les fonctions qui ralentissent son exécution. Dans 90% des cas, l'origine du ralentissement est la base de donnée. Il existe plusieurs solutions de profiling pour PHP:
Côté serveur, 2 debuger réalise les traces :
  • Zend Debuger
  • Xdebug
Les trace de Zend Debugger s'analyse avec Zend Studio
Avec Xdebug on a plusieurs outils disponibles, WinCacheGrind (sous windows) MacCallGrind(mac) et KCacheGrind (KDE linux). Il faut souligner la véritable supériorité de Kcachegrind sur les 2 autres outils. Ceci pour une raison simple, les graphs et la capacité à naviguer dans les graphs offre un confort et une facilité d'analyse bien supérieur à l'analyse de tableaux:

Dans ce graph, la surface occupé par une case est proportionnelle au temps cpu consommé.

Graphique d'appel


Le couple xdebug + Kcachegrind est la référence dans le domaine. Toutefois, Xdebug ne profile
plus la mémoire (l'auteur indiquant que le format de donné cachegrind n'étant pas adapté à cet usage), c'est une régression fonctionnelle regrettable. On peut toujours faire du profiling "à la main" avec xdebug mais c'est un résultat brut.

Côté alternative, XHProf est une application web développé par Facebook. XHProf nécessite la modification du code source php pour que le code soit 'tracé'. Ce qui peut-être ennuyeux si on 'oubli' de tracer un appel ajax qui serait invisible à l'analyse alors qu'il peut potentiellement être l'origine d'une non-performance. L'avantage de XHProf sur Xdebug, c'est le profiling de la mémoire utilisé. L'utilisation de XHProf en renfort du couple Xdebug + Kcachegrind

Test de chargement html

Côté utilisateur la perception du temps de réponse est la somme du temps de calcul par le serveur, du transit réseau et du temps de calcul par le navigateur. Ainsi, même un serveur rapide et peu chargé peu laisser une impression de lenteur quand les pages sont trop longues à s'afficher. Il est donc important de tester pour mesurer la performance complète du point de vue internaute.
L'un des points important est de permettre au navigateur web de commencer à calculer le rendu de la page même si celle ci n'est pas entièrement chargée. Pour firefox, firebug permet de visualiser le traitement des requêtes:
 
Sous internet explorer, Aol Page Test

AOL Test Page est un outil dédié à la mesure du chargement.




On savait les tests nécessaires, mais ce billet précise au moins 2 points :
- la nature des tests est très lié à l'architecture choisie
- les tests s'insèrent dans une démarche de génie logicielle