XHTML.net

Technology talks by Loïc d’Anterroches

News, articles, PHP, scripts, XHTML/CSS, …

Unit testing dans Pluf

# By Loïc d'Anterroches, May 09, 2008.

Ce matin, je vous donnais 8 astuces pour votre développement d’application web et cela en même temps me forçait à penser à un système robuste pour faire des tests unitaires dans une application développée avec Pluf. J’ai maintenant un bon mal de crâne, mais j’ai aussi implémenté un système basé sur SimpleTest. Lisez les commentaires de ma note de ce matin, vous verrez la coincidence… en effet l’auteur de SimpleTest est venu proposer d’utiliser SimpleTest…

Avec testrunner.php, vous pouvez lancer les tests de votre application.

$ php ./chemin/vers/testrunner.php VotreApp ./VotreApp/conf/votreapp.test.php

Cela va charger tous les tests disponibles dans le répertoire VotreApp/Tests ainsi qu’un niveau de sous-répertoires et les lancer.

Chaque fichier doit contenir une classe qui étant la classe UnitTestCase. Le nom de la classe doit suivre les conventions de Pluf, par exemple VotreApp_Tests_monTest pour une classe dans le fichier VotreApp/Tests/monTest.php.

Et voilà !

Maintenant, la vraie partie intéressante est la possibilité de tester vos vues sans avoir besoin d’un serveur web ! Pour cela, utilisez le client Pluf_Test_Client dans vos tests :

Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
$url = Pluf_HTTP_URL_urlForView('VotreApp_Views::accueil');
$client = new Pluf_Test_Client(Pluf::f('votreapp_views'));
$reponse = $client->get($url, array('param_optionel' => 'toto'));
$this->assertEquals(200, $response->status_code);
print $response->content;
print_r($response->template); 

L’objet $response vous donne accès au gabarit utilisé pour faire le rendu de la page. Il est disponible dans $response->template et le contexte associé est dans $response->template->context. Cela vous permet de regarder que le contexte a les bonnes valeurs.

Le client supporte les sessions. Donc si vous l’utilisez avec une méthode POST pour vous connecter, vous pouvez ensuite réutiliser ce client pour accéder une page qui demande d’être connecté, par exemple :

$client->post('/mapagedelogin/', array('login' => 'toto', 'password' => 'secret'));
$client->get('/mapageprivee/');

Bien entendu, c’est mieux d’utiliser Pluf_HTTP_URL_urlForView pour les URLs car cela évite de les coder en dur.

Bon, maintenant, il me reste à faire les choses suivantes :

  • Une méthode standard pour initialiser un environnement de test propre au moment de lancer le testrunner.
  • Convertir les tests de Pluf pour suivre la nouvelle infrastructure.
  • Convertir mon propre code pour suivre cette nouvelle infrastructure.
  • Faire la chasse aux bugs :)

8 astuces pour bien développer une application web

# By Loïc d'Anterroches, May 09, 2008.

Voici une petite liste de choses auxquelles il faut bien penser quand on développe une application web. Cela provient de toutes les erreurs que j’ai faites, donc bon, si vous avez d’autres idées, n’hésitez pas à les mettres en commentaires :

  1. Avoir un serveur de production, un de test et un de développement. Ils peuvent être sur la même machine physique au besoin, mais faites la séparation code, fichiers de données et base de données.
  2. Pensez à la localisation dès le début. Dans la majorité des cas, cela ne prend pas de temps de le faire dès le début, mais le faire à la fin est une galère incroyable.
  3. Une méthode pour créer un projet propre en une ligne de commande. Supposez que vous faites une application web pour votre site web, en une ligne de commande vous devez pouvoir installer un nouveau site web avec aucun contenu (au moins pour la partie fichiers de données, code et base de données, la configuration du serveur peut se faire à la main).
  4. Une commande à taper pour mettre en ligne une nouvelle version ou revenir en arrière. Utilisez un bon script shell, Fabric, Capistrano pour cela.
  5. Un environnement de test propre. Idem, en une commande vous devez pouvoir vous créer un environnement de test propre pour tester votre application.
  6. Tests unitaires. C’est le plus dur, d’ailleurs je n’ai toujours une bonne méthode dans Pluf pour faire ces tests. Pour le moment, j’utilise twill pour tester les vues et PHPUnit pour la logique.
  7. Gestionnaire de révisions. C’est maintenant un standard, mais c’est bon de le répéter, utilisez Subversion, CVS, Bazaar, Git ou autre chose, mais utilisez un gestionnaire de révisions de votre code et utilisez aussi un système pour gérer les révisions de votre schéma de base de données.
  8. Payez vos dettes régulièrement. Vous ne devez pas laisser du mauvais code dans votre système, ce mauvais code est une dette que vous traînez…

En gros, automatisez votre méthode de développement pour limiter les freins à l’évolution de votre code.

D’autres points auxquels il faut penser ?

Vary header avec Pluf

# By Loïc d'Anterroches, May 08, 2008.

Mes expériences avec le système de traduction de Pluf m’ont fait toucher à une en-tête (header) renvoyée par le serveur. Cet en-tête est Vary. Cet en-tête permet d’informer les navigateurs/agents ainsi que les proxy/caches que le contenu d’une page varie en fonction des en-têtes émis par l’agent faisant la requête.

Par exemple, mon navigateur, Firefox, envoie cela comme information quand il faut une requête :

Accept: text/xml,application/xml,application/xhtml+xml,
        text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: en,fr;q=0.7,en-us;q=0.3

Cela veut dire, j’accepte du contenu dans différents formats, avec la priorité au format text/xml, puis application/xml, … et à la find image/png et enfin */* tout ce qui reste. Le navigateur accepte aussi le contenu en Anglais, puis Français et Anglais américain. Vous noterez pour certains cas la présence d’un paramêtre q avec une valeur. Ce paramêtre q, comme qualité, avec les langues s’explique de la façon suivante :

en,fr;q=0.7,en-us;q=0.3 correspond en étendu à en;q=1,fr;q=0.7,en-us;q=0.3

Maintenant supposons que vous puissiez accéder au site http://example.com/mon_sujet/ et que ce dernier dispose de 3 versions, une en anglais, une en français et une en américain. Le serveur va maintenant devoir choisir quelle page retourner. Comment choisir ?

Il faut d’abord attribuer une valeur aux trois versions. La valeur correspondera par exemple à la quantité d’information disponible. On va dire que par exemple, la page en Anglais à une qualité de 0.5, la page française de 0.9 et la page américaine de 1.0.

Le résultat du calcul pour chaque langue est le suivant :

  • Anglais : 1.0x0.5 = 0.5
  • Français : 0.7x0.9 = 0.63
  • Américain : 0.3x1.0 = 0.3

C’est donc la page française avec le score le plus haut qui sera retournée. Maintenant, c’est à vous de donner en interne un score pour chaque version de vos pages… en pratique les personnes donnent un score de 1 pour chaque version et donc retourne dans notre cas la version anglaise.

Vous pouvez lire un peu plus sur ces en-tête ici : RFC 2616, Header Field Definitions.

Donc maintenant, vous comprenez que la page http://example.com/mon_sujet/ ne va pas toujours être la même et que le contenu varie en fait par rapport à l’en-tête Accept-Language. Si vous avez maintenant un système de cache naïf, qui stocke une version par URL, cela ne marche pas, car finalement une seule des 3 versions va être mise en cache… problème !

C’est là que l’en-tête Vary du serveur intervient. Cet en-tête dit au système de mise en cache, attention, le contenu de cette page varie en fonction de certaines caractéristiques de la requête. C’est pourquoi on renvoie l’en-tête suivant :

Vary: accept-language

Si vous renvoyez une page qui est en xml ou html en fonction de la requête, vous pourriez mettre :

Vary: accept-language, accept

Il faut donc dans Pluf un moyen élégant de mettre à jour cet en-tête. Je n’ai trouvé qu’une solution pour le moment, mettre dans l’objet requête l’information de la manière suivante :

$request->response_vary_on = 'accept';

Pourquoi sur la requête et pas ajouter manuellement dans la réponse ? Vous pouvez le faire, mais souvent, on n’a pas accès à l’objet réponse. En effet, une vue typiquement ce termine par l’appel suivant :

function mavue($request, $match) 
{ 
    .....
    return Pluf_Shortcuts_RenderToResponse('mongabarit.html', 
                              array('param1' => $toto, ...));
}

Comme vous ne touchez pas à l’objet réponse, il faut trouver une autre solution élégante. Et comme l’objet $request est disponible à tous les niveaux du dispatcher, j’ai donc choisi cette option :

function mavue($request, $match) 
{ 
    .....
    $request->response_vary_on = 'accept';
    return Pluf_Shortcuts_RenderToResponse('mongabarit.html', 
                               array('param1' => $toto, ...));
}

Le problème est que cela fait une condition if supplémentaire pour toutes les vues, que l’en-tête Vary soit utilisé ou non. Cela pose donc une question de performance. Je vais voir si je trouver une méthode plus élégante.

Petit plaisir du système de traduction

# By Loïc d'Anterroches, May 08, 2008.

Avec le système de traduction, voici une réponse type :

Date: Thu, 08 May 2008 12:10:14 GMT
Server: Apache/2.2.4 (Ubuntu) PHP/5.2.3-1ubuntu6.3
X-Powered-By: Pluf - http://pluf.org/
Vary: Accept-Language
Content-Language: fr
Content-Length: 19263
Keep-Alive: timeout=15, max=90
Connection: Keep-Alive
Content-Type: text/html; charset=utf-8

200 OK

Le système retourne bien la langue du contenu et indique bien que le contenu de la page varie en fonction de l’en-tête Accept-Language du l’agent faisant la requête. C’est chouette… Le choix de la langue se fait en cherchant dans l’ordre :

  1. la clef pluf_language dans la session ;
  2. un cookie pluf_language (le nom peut être changé dans la configuration de l’application) ;
  3. la configuration du navigateur/agent faisant la requête ;

La langue séléctionnée est disponible dans $request->language_code et est utilisée pour écrire l’en-tête Content-Language dans la réponse. Bon, pour le moment, l’en-tête Vary est écrasé par le middleware, je vais devoir mettre à jour et non écraser. Mais bon, ce n’est pas trop important pour le moment.

Le système de traduction dans Pluf

# By Loïc d'Anterroches, May 08, 2008.

Et voilà, c’est en ligne la doc pour traduire vos applications avec Pluf. Voici un exemple de gabarit traduit :

<h1>{trans "Pluf internationalization"}</h1>
{assign $n_methods = $methods.count()} 
<p>{blocktrans $n_methods}To translate your code, use 
  the following method:{plural}To translate your code, use 
  one of the {$n_methods} methods:{/blocktrans}</p>
<ul>
{foreach $methods as $method}
  <li>{blocktrans}Name: {$method.name}, 
  Description: {$method.description}.{/blocktrans}</li>
{/foreach}
</ul>

Grosso modo, vous avez la fonction trans pour une ligne simple à traduire, blocktrans pour du multi ligne avec substitution de variables et pour les cas pluriels. Dans le code vous pouvez utiliser __() et __n().

Pour activer la locale, il suffit d’activer le middleware Pluf_Middleware_Translation et d’avoir la liste des langues supportées par l’application dans la configuration: $cfg[‘languages’] = array(‘en’, ‘fr’, ‘fr_QC’,);

Le middleware va automatiquement détecter le langage adapté pour le navigateur et retourner la bonne traduction.

Logo of Plume CMS