%BEGINLATEX%
Principes de base de MVC et du développement Web rapide avec Catalyst
Marc SCHAEFER
Revision: r10 - 22 Nov 2012 - 13:55:13 - MarcSCHAEFER
Principes de MVC. Framework Catalyst. Développement rapide.Templates. Exemples participatifs. CRUD. ORM.
%STOPLATEX%
%STOPPUBLISH%
%STARTPUBLISH%
%STARTLATEX%
Slide 1: Cette présentation
Principes de base de MVC et du développement Web rapide avec Catalyst
Marc SCHAEFER
HE-Arc Ingénierie / ISIC
Slide 2: Vers MVC
La création d'applications Web modernes a des nouveaux besoins
- intégration de designs avancés
- affichage sur plusieurs types de terminaux (PC, mobile, ...)
- intégration de toolkits pour des fonctions riches (Web 2.0): AJAX (JQuery, Google API, ...)
- sécurité, réutilisabilité du code
- rapidité de développement
- évolution vers l'approche service REST
framework == bibliothèque standard + conventions et structuration + inversion du flot + comportement par défaut
Slide 3: Principes des frameworks de type MVC
- indépendance de la présentation (View)
- séparation logique métier (Model) de l'agencement des flux
- contrôleur léger/lourd (fat controller)
- fonctions standards intégrées
- validation et échappement des données (sécurité, transparence)
- sessions et contexte (suivi)
- routage d'URL géré globalement
- contrôle d'accès global
- centralisation de la configuration
- test unitaire
- déploiement
Slide 4: Motivation du choix du framework Catalyst (1)
- langage Perl
- langage universel (non spécifique au Web)
- multiplateforme (préinstallé sur les systèmes GNU/Linux)
- framework orienté objet post-moderne Moose
- large bibliothèque de logiciels (CPAN)
- licence GPL ou Artistic
- implémenté en C
- large communauté, assez populaire (suivant les classements, arrive proche de Python et PHP, pas loin de C++ et toujours avant Ruby)
Slide 5: Motivation du choix du framework Catalyst (2)
- framework Catalyst
- framework mi-lourd, basé sur l'intégration de nombreuses bibliothèques existantes
- ORM (Model): DBIx (p.ex.)
- templating (View): Template Toolkit (p.ex.)
- certaines fonctions sont implémentées par des extensions du langage Perl (p.ex. le routage d'URLs)
- à peu près la seule implémentation Perl complète d'un framework MVC
- grande communauté
- alternatives similaires (MVC)
- Ruby (on Rails)
- PHP (p.ex. Synfony)
- Java Struts (ou quelque chose de plus récent)
Comments
Un travail de semestre a été effectué à la HE Arc ingénierie, comparant 3 frameworks Web MVC
(G. Schindelholz, 12INF-TP206).
Même si certains aspects de ces frameworks sont difficilement comparables (p.ex. leurs
outils de templating et d'ORM sont différents, leur système de déploiement et de packaging
également), leurs principes de base sont similaires.
La comparaison donne en très grand résumé les résultats suivants:
Slide 6: Exemple: création d'une application Catalyst Hello
pour l'exemple, depuis le GUI de la machine virtuelle Catalyst proposée:
- lancer un terminal: ALT-F2, puis
xterm
- créer un répertoire de travail:
mkdir work
- entrer dans ce répertoire:
cd work
création d'une application Catalyst Hello
-
catalyst.pl Hello
-
cd Hello
Slide 7: Découverte de l'application simpliste Hello
- promenez-vous dans les fichiers générés par le framework et identifiez ce qui est du domaine de la configuration, de la gestion de version, de scripts prégénérés, du test, du code, et des données statiques.
- identifiez dans lib/Hello les 3 bibliothèques de composants MVC
- la plupart sont vides pour le moment, mais consultez lib/Hello/Controller/Root.pm
- en revenant à la racine de l'application Hello, lancez-la en observant les messages puis testez-la en observant à nouveau les messages
-
script/hello_server.pl -r
(serveur interne rapide)
- sur la machine réelle allez à l'URL http://IP:3000/ (avec IP l'adresse IP de la VM)
- vous devriez voir l'exécution de la requête, son routage et le résultat dans le client WWW
- à partir de là, il vaut la peine de lancer un nouvel
xterm
comme précédemment et d'y travailler.
Slide 8: Routage d'URL
- actuellement Root.pm définit les méthodes index, default et end
- index: correspond à l'URL relatif /
- default: exécuté lorsqu'il n'y a pas de routage défini
- end: triggers d'actions par défaut
- Catalyst propose plein de façons de faire cela, la plus simple est le qualificateur :Global dans Root.pm
Comments
Il faut ajouter la méthode suivante à lib/Hello/Controller/Root.pm, le plus simple
est en double-cliquant depuis Nautilus:
sub hello :Global {
my ( $self, $c ) = @_;
$c->response->body("Hello, World!");
}
On peut sauver, puis directement tester:
http://IP:3000/hello
Attention: rien que ce texte est retourné (plus quelques entêtes classiques HTTP).
Il manque toute la "glue" HTML.
Slide 9: Indépendance de la présentation (View)
Produire une sortie complexe indépendante du reste de l'application
langages
- XHTML 1.0
- HTML 5
- Javascript
- e-mail, SVG, LaTeX, PDF, ...
plateformes
- client WWW classique, embarqué, mobile, ...
solution
- langage intermédiaire paramétré (templating)
- exemples: PHP, JSP, Perl::Mason, Template::Toolkit, XSLT XML, ...
Slide 10: Exemple: création d'une vue en Template::Toolkit
création d'une vue nommée HTML, de type TT
-
script/hello_create.pl view HTML TT
- consulter le fichier
lib/Hello/View/HTML.pm
et voir ce qu'il définit
création d'un template proprement dit
utilisation de ce template
test de cette vue
(à nouveau il n'y a pas encore la glue HTML qu'il faudrait dans un cas réel)
Comments
Il y a de la magie interne
- la méthode end() de Root.pm se charge d'afficher le template, s'il est défini et que l'on n'a pas assigné $c->response- >body()
Slide 11: Template par défaut
But
Idée de Template::Toolkit
- inclusion de templates via un wrapper
Principes
- configurabilité du wrapper par variables
- valeurs par défaut intéressantes
Comments
Tout d'abord créer le wrapper
Références
Slide 12: Paramétrisation d'un template
But
A faire
- modifier votre template hello.tt pour qu'il contienne l'expansion d'une variable du stash, par exemple
[% username or "inconnu" %]
- modifier votre méthode hello() de Root.pm pour obtenir ce paramètre et le stocker dans le stash: la façon la plus générale -- et la moins contrainte -- Catalyst est la suivante:
sub hello :Global :Args {
my ($self, $c, %args) = @_;
$c->stash(username => $args{"username"},
template => 'hello.tt');
}
- tester votre template, p.ex. avec (http://IP:3000/hello/username/marc)
Sécurité
Slide 13: Indépendance métier / données: Model
- interface générique pour accéder des données (CRUD: créer, lire/rechercher, modifier, effacer)
- par exemple pour des BD relationnelles
- indépendance du dialecte de chaque BD
- implémentation de fonctions manquantes éventuelles
- souvent utilisation d'une correspondance objet-relationnel (ORM)
- avec un contrôleur léger, l'essentiel du travail est fait dans le modèle (couche métier)
- Catalyst peut générer les ORM en fonction du schéma de la BD
- parfois, on peut/doit compléter certains détails (p.ex. relations IS-A)
Slide 14: Exemple: petite base de données et début de CRUD
Cet exemple est simplifié; on ne montrera pas par exemple de liens entre tables (qui sont
gérables par l'ORM).
- création d'une BD SQLite (terminer par CTRL-D)
sqlite3 hello.db
CREATE TABLE personne(id INTEGER PRIMARY KEY, nom TEXT NOT NULL, pw TEXT NOT NULL);
INSERT INTO personne(nom, pw) VALUES('marc', 'demo');
INSERT INTO personne(nom, pw) VALUES('alex', 'toto');
SELECT * FROM personne;
Slide 15: Exemple: affichage des données de la BD
Vue d'affichage d'une liste de tuples
Etapes
- créer le modèle (slide précédent)
- créer le contrôleur Personne (grouper toutes les fonctions agissant sur une personne)
- créer un template listant les tuples
- créer la méthode lister() dans le contrôleur personne, utilisant le modèle et exportant les données vers le template (pour affichage HTML)
Comments
Commençons par créer le contrôleur Personne
script/hello_create.pl controller Personne
Ensuite, définissons un nouveau template permettant de voir la liste des tuples: root/personne/lister.tt (
mkdir root/personne
avant)
[% IF personnes.size < 1 %]
<p>Aucun utilisateur à afficher</p>
[% ELSE %]
[% cols = [ 'id', 'nom', 'pw' ] %]
<table class="stdlist">
<tr>[% FOREACH h IN cols %]<th>[% h %]</th>[% END %]</tr>
[% # Display each user in a table row %]
[% FOREACH u IN personnes -%]
<tr>
[% FOREACH h IN cols %]<td>[% u.$h | html %]</td>[% END %]
<td>
<a href="[% c.uri_for(c.controller.action_for('modifier'), [u.id]) %]">E</a><a href="[% c.uri_for(c.controller.action_for('supprimer'), [u.id]) %]">D</a>
</td>
</tr>
[% END -%]
</table>
[% END %]
Le template ci-dessus propose déjà des fonctions d'effacement ou de mise à jour, non implémentées, dont les URLs seront générés en fonction de la structure du contrôleur, lorsque les méthodes ad-hoc seront implémentées (plus loin).
Mais tout d'abord, créons la méthode lister() dans le contrôleur Personne, qui correspond à
l'URL /lister relativement à /personne:
=head2 lister
=cut
sub lister :Local {
my ($self, $c) = @_;
$c->stash(personnes => [ $c->model('DB::Personne')->all ],
title => 'Liste des personnes',
template => 'personne/lister.tt');
}
On peut maintenant tester cette fonction avec
http://IP:3000/personne/lister
Slide 16: Exemple avancé CRUD: suppression par chaînage
Les méthodes d'effacement et de modification ont besoin de trouver la personne
par son ID.
Le chaînage Catalyst permet de regrouper cette fonctionnalité dans le routage
d'URL.
Principes
- on ajoute une dépendance à la déclaration de la méthode
- on implémente la dépendance
- Catalyst fait le reste
Par exemple, via un URL générique
http://IP:3000/personne/id/ID/operation
Effacement
Comments
Implémentons tout d'abord la méthode qui trouve une personne avec son id:
=head2 object
Cherchons la personne par son id.
=cut
sub object :Chained('base') :PathPart('id') :CaptureArgs(1) {
my ($self, $c, $id) = @_;
$c->stash(object => $c->stash->{resultset}->find($id));
if (!$c->stash->{object}) {
$c->detach('/error_404');
}
}
Mais cette méthode a besoin notamment du modèle dans le
stash sous resultset. Tout cela peut s'initialiser par la méthode
base() qui est d'ailleurs déjà chaînée ci-dessus.
=head2 base
Lier le Model (commun)
=cut
sub base :Chained('/') :PathPart('personne') :CaptureArgs(0) {
my ($self, $c) = @_;
# instancier le Model
$c->stash(resultset => $c->model('DB::Personne'));
}
Implémentons maintenant la méthode d'effacement
sub supprimer :Chained('object') :PathPart('supprimer') :Args(0) {
my ($self, $c) = @_;
$c->stash->{object}->delete;
$c->stash->{message} = 'Suppression OK';
$c->detach('lister');
}
L'effacement est une fonction très simple. Pour le moment nous ne demandons pas de confirmation. La confirmation pourrait être ajoutée
côté client (Javacript).
Notons que plutôt que de faire un detach(), qui permet de voir l'URL d'effacement -- ce qui n'est pas idéal vu l'effet de bord de la
fonction -- une redirection HTTP est recommandée. Dans ce cas,
le message de status devrait être passé soit comme paramètre, soit dans la session comme dans l'exemple ci-dessous:
$c->flash->{message} = 'Effacement effectué';
$c->res->redirect($c->uri_for("lister"));
$c->detach;
Ce qui précède suppose qu'une session soit maintenue.
Slide 17: Exemple avancé CRUD: insertion
Etapes
- utiliser le chaînage pour effectuer l'instanciation du modèle dans base (pas traité ici!)
- la validation des données peut être automatisée (pas traité ici!)
- du côté client: Javascript, HTML5 (facultatif)
- du côté serveur: divers modules Catalyst (obligatoire)
Comments
Tout d'abord, implémentons le bas niveau, soit l'insertion proprement dite, dans le contrôleur Personne. Cette insertion a un effet de bord,
c'est pour cela que nous agissons ici sur le cache du client WWW. Une autre possibilité serait d'utiliser un POST, ou d'effectuer une
redirection HTTP.
=head2 ajouter
Ajouter une personne
=cut
sub ajouter :Local {
my ($self, $c, $nom, $pw) = @_;
my $personne = $c->model('DB::Personne')
->create({ nom => $nom, pw => $pw });
# Suppression du cache
$c->response->header('Cache-Control' => 'no-cache');
$c->stash->{message} = 'Ajout OK';
$c->detach('lister');
}
Testez avec
http://IP:3000/personne/ajouter/NOM/PW
Slide 18: Exemple avancé CRUD: insertion par formulaire
- si NOM ou PW ne sont pas spécifiés, présenter un formulaire (nouveau template) qui envoie les données via POST sur une action
- ajouter l'URL d'ajout par exemple dans le template de listage et tester!
Comments
Il suffit d'ajouter le traitement si NOM et PW ne sont pas spécifiés: soit afficher un formulaire d'ajout:
sub ajouter :Local {
my ($self, $c, $nom, $pw) = @_;
if (!defined($nom) || !defined($pw)) {
$c->stash(template => 'personne/ajouter.tt',
title => 'Ajouter une personne',
nom => $nom || '',
pw => $pw || '');
}
else {
my $personne = $c->model('DB::Personne')
->create({ nom => $nom, pw => $pw });
# Suppression du cache
$c->response->header('Cache-Control' => 'no-cache');
$c->stash->{message} = 'Ajout OK';
$c->detach('lister');
}
}
Le template lui-même (root/personne/ajouter.tt):
<form action="[% c.uri_for('ajouter_action') %]" method="post">
<label for="nom">Nom</label>
<input type="text" name="nom" id="nom" value="[% nom | html %]" />
<label for="pw">Mot de passe</label>
<input type="password" name="pw" id="pw" value="[% pw | html %]" />
<input type="submit" value="Ajouter" />
</form>
Il faut maintenant implémenter la méthode ajouter_action() dans le contrôleur Personne, cette
fois en utilisant le chaînage:
=head2 ajouter_action
Traiter un formulaire POST d'ajout d'une personne.
=cut
sub ajouter_action :Chained('base') :PathPart('ajouter_action') :Args(0) {
my ($self, $c) = @_;
my $nom = $c->request->params->{nom} || 'N/A';
my $pw = $c->request->params->{pw} || 'N/A';
$c->stash->{resultset}->create({ nom => $nom, pw => $pw });
$c->stash->{message} = 'Ajout OK';
$c->detach('lister');
}
Il manque certes le traitement d'erreur. Nous le laissons de côté car il existe des
validateurs automatiques du côté Catalyst.
Enfin, il suffit de modifier le template de l'affichage de la liste de tuples (root/personne/lister.tt) pour y ajouter le lien à ajouter:
<p><a href="[% c.uri_for(c.controller.action_for('ajouter')) %]">ajouter</a>
Slide 19: Exemple avancé CRUD: modifier
Pour modifier, il faut afficher (un formulaire) sur le tuple puis traiter la modification dans le modèle.
Réflexions
- le chaînage semble utile ici (pour trouver l'objet)
- pourquoi ne pas réutiliser le template du formulaire d'ajout ?
- il y a peut-être des similarités avec l'ajout, à réfléchir (mais pas traité ici)
- visualiser un tuple est une version dégradée de la modification (pas traité ici)
Comments
Notre méthode:
=head2 modifier
Modifier une personne.
=cut
sub modifier :Chained('object') :PathPart('modifier') :Args(0) {
my ($self, $c) = @_;
$c->stash(template => 'personne/ajouter.tt',
title => 'Modifier une personne',
action => $c->uri_for($c->controller->action_for('modifier_action'), [$c->stash->{object}->id]),
nom => $c->stash->{object}->nom,
pw => $c->stash->{object}->pw);
}
Il faut cependant modifier root/personne/ajouter.tt:
<form action="[% action or c.uri_for('ajouter_action') %]" method="post">
(et éventuellement modifier aussi la value du bouton)
Et traiter le formulaire:
=head2 modifier_action
Traitement du formulaire POST.
=cut
sub modifier_action :Chained('object') :PathPart('modifier_action') :Args(0) {
my ($self, $c) = @_;
my $nom = $c->request->params->{nom} || 'N/A';
my $pw = $c->request->params->{pw} || 'N/A';
$c->stash->{object}->update({ nom => $nom, pw => $pw });
$c->stash->{message} = 'Modification OK';
$c->detach('lister');
}
Slide 20: Validation des données
Idéalement: les données sont typées, et Catalyst les valide du côté serveur, présentant en rouge les valeurs incorrectes.
Pistes
Slide 21: Sessions
Session == contexte
Implémentations possibles
- cookie référençant un fichier ou une base de données
- paramètres de GET et champ cachés de POST, maintenus manuellement
Catalyst
- propose un stash persistant (flash)
- copiable (et effacé) à chaque requête
- quasi plug-and-play
Principes
- ajouter les modules Catalyst pour la gestion de la session
- ajouter
flash_to_stash 1
dans le fichier de configuration
Références
Comments
Modifions la méthode ajouter() du contrôleur Personne pour effectuer une redirection
sub ajouter :Local {
my ($self, $c, $nom, $pw) = @_;
if (!defined($nom) || !defined($pw)) {
$c->stash(template => 'personne/ajouter.tt',
title => 'Ajouter une personne',
nom => $nom || '',
pw => $pw || '');
}
else {
my $personne = $c->model('DB::Personne')
->create({ nom => $nom, pw => $pw });
$c->stash->{message} = 'Ajout OK';
$c->response->redirect($c->uri_for('lister'));
return 0;
}
}
Tester avec
A partir de là, on voit que le message "Ajout OK" ne s'affiche plus.
Par contre, l'URL affiché par le navigateur est bien maintenant
la fonction lister.
Mettons en place les sessions
En testant à nouveau, on verra que le message est bien affiché malgré la redirection.
Pour info, par défaut les informations de sessions seront stockées dans
/tmp/hello/session_data.
Slide 22: Contrôle d'accès (login)
- agir sur les routes (plutôt que d'abuser des "if")
- la gestion est effectuée par un module générique Catalyst (session, login, logout) à particulariser pour notre application
- man Catalyst::Plugin:Authentication
Références
Slide 23: Exemple: authentification et autorisation
- page de login (contrôleur, template, traitement du formulaire)
- utilisation de notre table personne pour l'authentification
- limitation globale de l'accès au contrôleur Personne si pas authentifié (redirection sur page de login) via la méthode auto().
Non traités
- stockage des mots de passe de manière hashée
- gestion de rôles plus fine (Authorization::Roles)
Comments
Tout d'abord, créer un contrôleur Login:
script/hello_create.pl controller Login
Ensuite créer la vue contenant le formulaire
root/login/login.tt
:
[% META title = 'Login' %]
<!-- Login form -->
<form method="post" action="[% c.uri_for('/login') %]">
<table>
<tr>
<td>Login:</td>
<td><input type="text" name="username" size="40" /></td>
</tr>
<tr>
<td>Mot de passe:</td>
<td><input type="pw" name="pw" size="40" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" name="submit" value="Se connecter" />
</td>
</tr>
</table>
</form>
(oh, quelle horrible mise en page avec un tableau! n'hésitez pas à faire mieux
en CSS)
Implémenter le contrôleur et la logique de base
=head2 login
Check the login is valid, if yes, redirect to /personne/lister.
Else, display an error message and this form again.
=cut
sub login : Global {
my ($self, $c) = @_;
my $username = $c->request->params->{username} || '';
my $pw = $c->request->params->{pw} || '';
if ($username && $pw) {
if ($c->authenticate({ nom => $username,
pw => $pw })) {
$c->response->redirect($c->uri_for('/personne/lister'));
return;
}
else {
$c->stash(error => 'Login incorrect.');
}
}
$c->stash(template => 'login/login.tt');
}
=head2 logout
=cut
sub logout : Global {
my ($self, $c) = @_;
$c->logout;
$c->response->redirect($c->uri_for('/'));
}
Ne pas oublier de supprimer la méthode index() du contrôleur Login pour que l'URL
/login/ soit assignée à la méthode login() -- ce qui est ce que nous avons prévu dans
le template ci-dessus (action du form).
Nous utilisons des méthodes génériques (authenticate(), user_exists()) qui se trouvent dans le module
Authentication. Il faut donc l'ajouter dans lib/Hello.pm (multi-ligne
use Catalyst
). Sinon
il y aura une erreur. De plus, il faut configurer
ce module. Ajouter à hello.conf (pourrait aussi se faire dans lib/Hello.pm)
<authentication>
default_realm dbic
<realms>
<dbic>
<credential>
# Specify that we are going to do password-based auth
class Password
# This is the name of the field in the users table with the
# password stored in it
password_field pw
# We are using an unencrypted password for now
password_type clear
</credential>
<store>
class DBIx::Class
user_class DB::Personne
</store>
</dbic>
</realms>
</authentication>
Tester que la vue s'affiche:
http://IP:3000/login/login, que le login et le logout
fonctionnent:
http://IP:3000/logout -- logout est :Global.
Implémenter la limitation globale de l'accès (si pas authentifié, l'accès à autre chose que
le contrôleur Login est impossible) (à implémenter dans
lib/Hello/Controller/Root.pm
):
=head2 auto
Auto hook to enforce access restrictions (login).
Note that 'auto' runs after 'begin' but before your actions and that
'auto's "chain" (all from application path to most specific class are run)
See the 'Actions' section of 'Catalyst::Manual::Intro' for more info.
=cut
sub auto :Private {
my ($self, $c) = @_;
# Allow unauthenticated users to reach the login page. This
# allows unauthenticated users to reach any action in the Login
# controller. To lock it down to a single action, we could use:
# if ($c->action eq $c->controller('Login')->action_for('index'))
# to only allow unauthenticated access to the 'index' action we
# added above.
if ($c->controller eq $c->controller('Login')) {
return 1;
}
# If a user doesn't exist, force login
if (!$c->user_exists) {
# Dump a log message to the development server debug output
$c->log->debug('***Root::auto User not found, redirecting to /login');
# Redirect the user to the login page
$c->response->redirect($c->uri_for('/login'));
# Return 0 to cancel 'post-auto' processing and prevent use of application
return 0;
}
return 1;
}
Tester que lorsqu'on accède à /login, cela fonctionne, mais si l'on accède autre chose, nous
sommes redirigés au login.
Modifier le template wrapper pour que l'on affiche le nom de l'utilisateur loggué si c'est le cas. Ajouter
aussi un lien pour la fonction de logout:
[% IF c.user_exists %]
<a href="[% c.uri_for('/logout') %]">Logout [% c.user.nom | html %]</a>
[% END %]
.
Tester maintenant que l'on est bien redirigé si l'on n'est pas loggué, et que le login (y compris
l'affichage du nom) puis le logout via le lien fonctionnent.
NB: si un "mock up" est nécessaire pour le test par exemple, il est possible de le faire de manière compatible avec
Catalyst::Plugin::Authentication via la configuration de l'application. Il y a un exemple dans la manpage, utilisant
un Perl hash.
Slide 24: Configuration
- important pour le déploiement / test
- plusieurs modules de configuration disponibles avec divers formats (YAML, simple, XML, ...)
par exemple
- configurer la BD de production ou de test
- utilisation de "mock up" pour un test partiel avant l'implémentation complète
- plusieurs instances de l'application, par entreprise
Slide 25: Test
Jusqu'ici, nos tests étaient manuels, mais pourquoi ne pas les automatiser, au moins partiellement ?
Types de tests
- unitaires (un module Perl, une méthode spécifique)
- applicatifs
- applicatifs avec interaction (cookies, etc)
Justification
- les tests automatiques peuvent se lancer ... automatiquement (à chaque release? à chaque commit?)
- plus vous implémentez de test, plus vous avez confiance dans votre application
- aussi: plus vous penserez que mettre à jour les dépendances de votre application est facile
- pas de test: peur du changement
Catalyst crée des tests simplifiés par défaut (que nous avons déjà cassés!)
Réalisons un test simple pour notre application.
Méthode extrême:
TDD (Test-driven Development)
Comments
Le premier problème que nous allons rencontrer est l'authentification: la plupart des tests générés par
Catalyst et lançables via
prove --lib lib t
vont planter.
Il faudrait soit travailler en "mock up", soit effectivement effectuer l'authentification. Nous laissons cela au lecteur
On peut effectuer des tests unitaires, ou des tests complets de l'application (incluant des interactions avec
le client), simulées, voire mécanisées (avec du vrai trafic Web).
Références
Création d'un test assez global (dans
t/all_app.t
) mais sans véritable trafic Web
#! /usr/bin/perl
use strict;
use warnings;
use Test::More;
BEGIN { use_ok("Test::WWW::Mechanize::Catalyst" => "Hello") }
my $ua1 = new Test::WWW::Mechanize::Catalyst();
my $ua2 = new Test::WWW::Mechanize::Catalyst();
$_->get_ok("http://localhost/", "Check redirect of base URL") for $ua1, $ua2;
$_->title_is("Login", "Check for login title") for $ua1, $ua2;
# pas ideal: serait mieux de tester un texte qui dirait que l'on n'est
# pas loggue; ou modifier login.tt pour si deja loggue ne pas presenter
# le formulaire.
$_->content_contains("Se connecter",
"Check we are NOT logged in") for $ua1, $ua2;
# Log in as each user
# Specify username and password on the URL
# THIS IS WHERE A TEST DATABASE COULD BE HANDY (in config e.g.)
$_->submit_form(
fields => {
username => 'toto',
pw => 'tutu'
}) for $ua1, $ua2;
# Go back to the login page and it should show that we are already logged in
$_->get_ok("http://localhost/login", "Return to '/login'") for $ua1, $ua2;
$_->title_is("Login", "Check for login page") for $ua1, $ua2;
$_->content_contains("Logout ",
"Check we ARE logged in" ) for $ua1, $ua2;
$_->get_ok("http://localhost/personne/lister", "personne list") for $ua1, $ua2;
$_->content_contains("Liste des personnes", "Check for personne list title") for $ua1, $ua2;
$_->content_contains("Logout ",
"Both users should have a 'User Logout'") for $ua1, $ua2;
# 'Click' the 'Logout' link (see also 'text_regex' and 'url_regex' options)
$_->follow_link_ok({n => 1}, "Logout via first link on page") for $ua1, $ua2;
$_->title_is("Login", "Check for login title") for $ua1, $ua2;
# voir commentaire ci-dessus
$_->content_contains("Se connecter",
"Check we are NOT logged in") for $ua1, $ua2;
# on pourrait ajouter une personne, tester qu'elle existe, etc.
done_testing;
Exécution du test
-
CATALYST_DEBUG=0 prove -wl t/all_app.t
Slide 26: Documentation
- si la documentation n'est pas dans le code
- pas forcément à jour
- pas forcément dans un contrôle de version
- pas forcément un format exploitable (diff, etc)
première idée
- documenter dans un Wiki et/ou un suivi de tickets
idée usuellement mise en pratique
- pragma d'auto-documentation dans le code
- en Perl: par exemple POD (Plain Old Documentation)
- autres langages: phpdoc, javadoc, ...
Différencier la documentation de développement et d'exploitation
de la documentation initiale de conception
Comments
Perl POD: si vous avez utilisé les fameux pragmas head, cut, etc directement
dans votre code Perl, alors vous pouvez utiliser par exemple
pod2text lib/Hello/Controller/Personne.pm
pour générer un fichier texte; il y a d'autres formats possibles (LaTeX, *roff/man, ou HTML).
Slide 27: Modules
- installation de modules (packages ou CPAN)
- documentation (man, perldoc, Internet)
Slide 28: Debugging
- classique (sortie et logs, avec la fonction log de Catalyst et les niveaux de debug filtrable)
- avec le debugger Perl (breakpoints, ..)
- de bons tests unitaires, de scénarii ou d'application peuvent aussi aider!
Slide 29: Références
Slide 30: Questions et errata
- pourquoi des fois des tirets à la fin des expansions de TT ?
- la véritable raison est le comportement d'envoi de saut de ligne/caractères blancs
- documentation
- pourquoi un tiret avant Debug (dans lib/Hello.pm)
- fonctionnement de l'exemple stash/flash douteux: est-ce vraiment suffisant de mettre dans le stash ?
- augmenter la résolution graphique de la VM ?
- le premier argument d'une méthode d'un contrôleur est l'objet lui-même (le contrôleur, souvent $self), le deuxième (souvent $c) est l'application Catalyst.
%STOPLATEX%
--
MarcSCHAEFER - 01 Aug 2012