04 juillet 2008

GWT, tests junit et MVC


Lors de la réalisation d'un prototype basé sur GWT, je me suis intéressé à la testabilité du framework. GWTTestCase est très décevant car il utilise un navigateur "hosted mode" caché et lance tout le module GWT. C'est donc un bon support pour des tests d'intégration mais nettement trop lourd (et particulièrement lent) pour des tests unitaires.

Je reprend donc mes marque-pages, et je relis cet article et celui-ci.

Avec GWT 1.5, l'utilisation des types génériques permet de créer un modèle simple pour la mise en oeuvre du pattern "Supervising Controller" :

D'abord la notion d'événement, dédié à un composant donné (nb: à cause d'un bug de blogger, j'ai mis des crochets pour la déclaration des types génériques) :

  1. public abstract class Event[T]
  2. {
  3. private T source;
  4. public Event( T source ) {..}
  5. }

..la source qui produit ces événements :

  1. public interface EventSource[T]
  2. {
  3. void addListerner( Listener[Event[T]] listener );
  4. void removeListerner( Listener[Event[T]] listener );
  5. }

..et une classe abstraite pour construire nos SupervisingControllers :

  1. public abstract class SupervisingController
  2. implements Listener[Event[T]]
  3. {
  4. protected T view;
  5. public abstract Object getModel();
  6. public void setView( T view )
  7. {
  8. this.view = view;
  9. view.addListerner( this );
  10. }
  11. }

Il suffit ensuite de définir la "vue" par une interface, comme dans cet exemple ma pop-up de login :

  1. public interface LoginDialog extends EventSource[LoginDialog]
  2. {
  3. String getLogin();
  4. String getPassword();
  5. void hide();
  6. }

Le contrôleur, lui, étend notre SupervisingController en le typant avec l'interface de la vue "supervisée".

  1. public class LoginController
  2. extends SupervisingController[LoginDialog]
  3. {
  4. private LoginService service;
  5. ...

.. et sur réception d'un événement de type 'validation du formulaire de login', il n'a plus qu'à invoquer le service RPC de connexion, mettre à jour les éléments visuels qui vont bien, et fermer la popup :

  1. public void on( Event e )
  2. {
  3. loginService.login( view.getLogin(), view.getPassword(),
  4. new AsyncCallback[Utilisateur]()
  5. {
  6. public void onSuccess( Utilisateur result )
  7. {
  8. view.hide();
  9. }
  10. } );
  11. }


Gràce au génériques, il n'y a pas de cast à déclarer et le code reste donc plutôt simple.

Qu'est ce que ça change ? Et bien maintenant, avec un simple Mock pour mon interface LoginDialog, et un service RPC de test, je peux tester dans un test jUnit classique la logique IHM de mon contrôleur. Elle est triviale dans l'exemple, mais vous devinez que la gestion d'un formulaire de 20 champs avec des controles croisés peut devenir un enfer sans un bon mécanisme de test.

Ce qu'il manque pour faire "encore mieux" :
  • un mécanisme de data-binding entre les widgets et un "modèle". Il existe quelques initiatives opensource, dont le succés et la pérennité sont à évaluer.
  • un spring-gwt, qui permettrait de déclarer dans l'exemple ci-dessus l'injection de la vue et du service RPC via une annotation @Resource. Dis Monsier Rod Johnson, tu peux m'inclure ça dans la roadmap spring 3.0 ;-)