insideIT.fr : le blog des architectes IT de SFEIR

Aller au contenu | Aller au menu | Aller à la recherche

Tag - Google Web Toolkit

Fil des billets - Fil des commentaires

vendredi 25 mars 2011

Retour d'expérience d'un projet GWT : 8 Internationalisation (8/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Dans le cadre du projet, nous avons mis en place l'internationalisation de l'application. Voici la documentation officielle de Google :

Nous avons ajouté le module I18N dans le module GWT "Module.gwt.xml" :

<inherits name="com.google.gwt.i18n.I18N"/>


Internationalisation en JAVA

Nous avons créé 3 classes :

  • /src/main/java/bws/i18n/ModuleConstants.java de type Constants
  • /src/main/java/bws/i18n/ModuleConstantsWithLookup.java de type ConstantsWithLookup
  • /src/main/java/bws/i18n/ModuleMessages.java de type Messages

Ainsi que les fichiers properties suivant qui correspondent à la traduction par défaut :

  • /src/main/resources/bws/i18n/ModuleConstants.properties
  • /src/main/resources/bws/i18n/ModuleConstantsWithLookup.properties
  • /src/main/resources/bws/i18n/ModuleMessages.properties

Et pour la traduction en français :

  • /src/main/resources/bws/i18n/ModuleConstants_fr.properties
  • /src/main/resources/bws/i18n/ModuleConstantsWithLookup_fr.properties
  • /src/main/resources/bws/i18n/ModuleMessages_fr.properties

Les fichiers ".properties" doivent être au format UTF-8, et les apostrophe (') doivent être dédoublé pour les interfaces étendant l'interface Messages (pas pour les Constants)

Par exemple pour afficher un message internationalisé depuis du code java d'une classe cliente (GWT) vous devez respecter la procédure suivante :

1/ Créer une méthode dans ModuleConstants.java, par exemple :

String warnCloseWindow();

2/ Ajouter une entrée dans les fichiers ModuleConstants*.properties avec la traduction correspondante:

warnCloseWindow=Are you sure you want to close this window?

3/ Utiliser cette clé dans votre classe java :

Window.alert(ModuleConstants.CONSTANTS.warnCloseWindow());

Pour la classe ModuleConstantsWithLookup, cela fonctionne de la même façon (méthode + clé correspondante) mais il est également possible de récupérer la valeur d'une clé à partir de la clé et sans appelé la méthode directement. Comme dans le code suivant ou on cherche à récupérer la valeur pour la clé "theKey":

ModuleConstantsWithLookup.CONSTANTS.getString("theKey");

Pour la classe ModuleMessages, il est possible de donner des paramètres aux méthodes qui seront intégrer dans la traduction. Un texte ne se place pas forcement au même endroit en fonction de la traduction (au début ou à la fin).

On met le numéro du paramètre (en commençant à 0) entre accolades (ex {0}), il est ensuite possible d'appliquer un formatage ({1,number} ou {0,date,medium} : en utilisant le formatage de la langue courante). On peux aussi gérer les formes singuliers et pluriels :

String cartItems(@PluralCount int itemCount);

et dans le fichier ".properties" :

cartItems = There are {0,number} items in your cart.
cartItems[one] = There is 1 item in your cart.
cartItems[none] = There is no item in your cart.


Internationalisation avec UiBinder

Pour traduire les libellés contenus dans des écrans UiBinder*.ui.xml vous devez encadrer la chaîne de caractère avec des balises spécifiques. Il vous faut tout d'abord ajouter ceci à votre fichier *.ui.xml dans la balise ui:UiBinder: ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat' ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator" ui:generateLocales="default"

Pour avoir au final:

<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"    
 ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
 ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator"   
 ui:generateLocales="default"
 xmlns:g="urn:import:com.google.gwt.user.client.ui">
...
</ui:UiBinder>

Par exemple, pour traduire : "where you can build your own workspace with widget" Il faut remplacer la chaine par :

<ui:msg description="FourthSentence">where you can build your own workspace with widgets.</ui:msg>

Et ce va automatiquement créer une nouvelle clé pour ce libellé. Vous trouverez les fichier properties générés dans target/extra/module

Ils sont nommés avec le full qualified name du fichier java concaténé avec le nom du fichier ui.xml puis suffixés avant l'extension par GenMessages... Simplissime non ? Merci Google (Cela évoluera très prochainement dans les futures release de GWT pour être simplifié)

Par exemple:
module.client.homepage.HomePageMenuHomePageMenuUiBinderImplGenMessages.properties

Ensuite nous positionnons un fichier dans le répertoire ou se trouve le fichier ui.xml à internationaliser, par exemple pour HomePageMenu.ui.xml :

/src/main/java/module/client/homepage/HomePageMenuHomePageMenuUiBinderImplGenMessages.properties

et évidemment :
/src/main/java/module/client/homepage/HomePageMenuHomePageMenuUiBinderImplGenMessages_fr.properties


Utilisation simplifiée

Une autre technique consiste à utiliser une petite ruse de Google : Si le compilateur, ne trouve pas le fichier de properties selon le nom donnée, il va chercher dans le fichier :
/src/main/ressources/com/google/gwt/i18n/client/LocalizableResource.properties

ou pour la version française :
/src/main/ressources/com/google/gwt/i18n/client/LocalizableResource_fr.properties

Il est ainsi possible de mettre toutes les traductions des fichiers UiBinder dans ce fichier et avoir un gestion simplifier de toutes les traductions. Pour créer ce fichier, un développeur a créé un petit script Python pour scanner tous les fichiers "*.properties" générées par le compilateur et les ajouter dans ce fichier. Il fait ensuite un mixte avec les clés de langues déjà existantes, et indique dans un commentaire, les textes à traduire, et surtout ceux dont la description a changé et qu'il faut peut être revoir la traduction.

Le script python mergelocales.py est disponible ici

Il suffit de le lancer avec :

mergelocale.py target/extra/module src/main/ressources/com/google/gwt/i18n/client/

mardi 1 mars 2011

Retour d'expérience d'un projet GWT : 6 Ecrire des tests unitaires avec Mockito (6/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Tester une application GWT avec une architecture MVP permet de pouvoir écrire des tests JUnit en java pure sans utiliser l'implémentation GWT de JUnit qui démarre un serveur web et un navigateur pour lancer les tests unitaires. Ce qui prend du temps si l'on doit exécuter ses tests à chaque modification de l'application.

Pour qu'il soit possible d'écrire des tests en Java pure, il faut que le Présenter ne possède aucune référence à des classes du package Ui de GWT. C'est à dire toutes les classes qui étendent Widget et qui permettent d'afficher des informations, ce qui logiquement doivent être définie dans la vue. Il faut ainsi écrire un contrat entre le présenteur et la vue sous la forme d'une interface Display. Ce contrat ne doit posséder que des paramètres et valeurs de retour définie par une interface ou pouvant être utiliser par Java (pas de TextBox getLogin() mais un HasValues<String> getLogin() ou toute autre classe faisant référence à des Widget GWT)

De même, il n'est pas possible d'utiliser GWT.create() autrement dit, pour instancier les services RPC, les classes de Constantes pour l'internationalisation, etc ...

Il est donc nécessaire de faire ses instanciations en dehors du Presenter, et ne prendre en paramètre que l'interface de ceux-ci. Il deviens ainsi possible de créer une version spécifique pour les tests, en règle générale, on va écrire des classes qui implémentent les interfaces avec du code minimal pour réaliser nos tests (getter, setter, hash maps pour enregistrer les données, calcule simple ...). Ces classes doivent être maintenue lors que l'on modifie l'application, et peuvent avoir des bugs qui empêche le test de fonctionner correctement.

C'est là que Mockito prend toute sa puissance, en implémentant automatiquement ses interfaces et en ajoutant le comportement de ceux-ci.

Il faut ajouter la dépendance à Mockito à Maven :

     <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.8.1</version>
         <scope>test</scope>
     </dependency>
     <dependency>
         <groupId>org.mockito</groupId>
         <artifactId>mockito-all</artifactId>
         <version>1.8.5</version>
         <scope>test</scope>
     </dependency>

Les tests doivent être écrit avec JUnit 4 et être exécutés par le Runner spécifique de Mockito (si on veux utiliser les annotations automatique permettant de créer les mock et les arguments captor)


Mockito

Mockito est un générateur automatique de doublures. Il fonctionne sur le mode de l'espion :

  • On crée les mocks;
  • On décrit leur comportement
  • Ensuite, à l'exécution, toutes les interactions avec les mocks sont mémorisés ;
  • à la fin du test, on interroge les mocks pour savoir comment ils ont étés utilisés.

Toutes les méthodes de mockito sont des méthodes static de la classe Mockito, il est donc nécessaire d'importer statiquement ses méthodes pour simplifier l'écriture :

import static org.mockito.Mockito.*;

On crée un mock soit avec la méthode mock :

MonInterface mock = mock(MonInterface.class);

soit en utilisant l’annotation @Mock:

@Mock
MonInterface mock;

Il n'est pas nécessaire que MonInterface soit une interface, il est possible de mocker une classe (mock(Widget.class) permet d'utiliser une classe GWT dans le test dans que cela pose de problème, une version vide ayant les mêmes méthodes publique est créé par mockito)

On décrit ensuite le comportement attendu de notre mock :

when(mock.maMethode()).thenReturn(42);

On appelle les tests, qui font des interactions avec notre mock, et enfin on vérifie ses interactions

verify(mock).maMethode();

Cette dernière ligne vérifie que la méthode maMethode() a bien été appelé durant le test.


Définir le comportement d'un mock

Pour définir, le comportement d'un mock, on utilise when() auquel on passe en paramètre l'appel que l'on veux définir, c'est à dire l'appel de la méthode avec les paramètres que l'on doit répondre :

interface Add {
    public int add(int a, int b);
}

Si on veux émuler le comportement de notre méthode add qui additionne les deux paramètres, on va donner les résultats pour des paramètres données :

Add mockAdd = mock(Add.class);
when(mockAdd.add(1, 1)).thenReturn(2);
when(mockAdd.add(5, 7)).thenReturn(12);

Ainsi si dans notre test, on appelle mock.add(1, 1), le retour sera 2.

On peux donner plusieurs valeurs de retour qui seront retourné dans l'ordre :

Par exemple, si on veux simulé les retours de la methode random, on fera :

when(randMock.random()).thenReturn(5, 18, 86, 42);

Ainsi le premier appel de random() retournera 5, et le quatrième appel retournera 42 (ainsi que tous les suivants).

On peux aussi simuler l'envoie d'une exception avec la method thenTrow() :

when(mock.div(anyIn(),0)).thenTrow(new Exception("Division par zéro));

Qui lancera alors une exception lors que le deuxième paramètre est un zéro.


Vérifier les appels aux mock

La méthode verify() qui prend en parametre permet de vérifier qu'une méthode à bien été appeler :

verify(mock).mustCall(1)

Cette ligne permet de vérifier que la méthode mustCall a bien été appelé lors du test, et de plus permet de vérifier que le paramètre est bien 1.

On peux tester le nombre de fois où la methode a été appellé :

verify(mock, times(5)).add(anyString());

On vérifie ici que la méthode add a été appellé 5 fois avec une chaînes de caractères en paramètre.

Si on veux tester plus de choses sur les arguments, il est possible de les capter (les enregistrer) pour ainsi faire des tests plus poussé dessus :

ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(userservice).login(captor.capture());
assertEquals("login", captor.getValue().getLogin());

On peux aussi vérifier qu'il n'y a pas eu d'interaction avec un mock

verifyZeroInteractions(mock);


Utilisation avec GWT

Si on veux tester qu'un événements a bien été envoyé par GWT, on utilise les mock et les ArgumentCaptor :

@Captor
ArgumentCaptor<ValueChangeEvent<String>> valueChangeEventCaptor;

@Mock
ValueChangeEventHandler<String> handler;

@Test
void testValueChangeEventHandler() {
    TestClassWithValueChangeHandlers test = new TestClassWithValueChangeHandlers();
    //On s'enregistre dans l'eventBus à l'évènement click
    test.addValueChangeHandler(handler);
    //On appelle la methode à tester qui envois notre évènement ValueChange();
    test.setValue("newValue");
    //On vérifie que celui-ci a bien été appellé
    verify(handler).onValueChange(valueChangeEventCaptor.capture());
    //On test que la nouvelle valeur est bien danc l'object event :
    assertEquals("newValue",             valueChangeEventCaptor.getValue().getValue());
}

Références Documentation de Mockito

jeudi 10 février 2011

Retour d'expérience d'un projet GWT : 5 Implémentation Modèle-Vue-Présenteur (5/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Voyons maintenant comment créer une architecture Model-View-Presenter.

Le présenteur doit étendre la classe Presenter qui fournit des fonctionnalités de base à notre application. Comme l'accès au global event bus et à la factory de vue.

public class TestPresenter extends Presenter {

    /**
     * Interface represents the test view
     */
    public interface Display extends DisplayView {
        public void setUserName(String username);

        public HasClickHandlers getUserNameClickHandler();
    }
   
    // fields
    /** View */
    Display display;
   
    // constructor
    public TestPresenter(Display display, HandlerManager globalEventBus, DisplayFactory factory) {
        super(globalEventBus, factory);
        this.display = display;
       
        bind();
    }
   
    // Bind all event
    private void bind(){
                getGlobalEventBus().addHandler(
            OnUserLoggedEvent.getType(),
            new  OnUserLoggedEvent.Handler() {
            @Override
            public void onUserLogged(OnUserLoggedEvent event) {
                display.setUserName(event.getUser()
                        .getLogin());
            }
        });
        display.getUserNameClickHandler().addClickHandler(
            new ClickHandler() {
                public void onClick(ClickEvent event) {
                    getGlobalEventBus().fireEvent(
                        new OnUserLogout());
            }
        }       
    }

    public void go(Placer panel) {
        panel.setView(display);
    }
}

Nous avons ici l'architecture de base d'un presenter :

  • Le constructeur prend en paramètre toutes les interfaces qu'il va utiliser :
    • sa vue interfacé par l'interface interne au presenter : Display
    • le global event bus (Bus d'évènement global à l'application pour la communication entre les presenter)
    • la factory de vue (utilisée pour instancier les vues des sous-presenters)
    • Éventuellement des services RPC
  • la méthode bind() qui s'occupe d'enregistrer le présenteur aux évènements globaux de l'application (ici le login de l'utilisateur) et aux évènements de la vue (ici le click sur le label affichant le login de l'utilisateur).
  • la méthode go(), qui permet au présenteur de s'afficher
    • la classe Placer est une abstraction d'un Panel GWT, pour être complètement indépendant de GWT, cette interface possède une simple méthode setView qui prend en paramètre un DisplayView. L'interface DisplayView qui doit être étendu par toutes les vues demande juste d'implémenter la méthode asWidget() qui permet de retourner la vue (this) en tant que Widget GWT.

Créons maintenant la vue, en utilisant UiBinder :

public class TestView extends Composite implements TestPresenter.Display {

    private static TestViewUiBinder uiBinder = GWT
            .create(TestViewUiBinder.class);

    interface TestViewUiBinder extends
        UiBinder<Widget, TestView> {}

    @UiField Label label;
   
    public TestView() {
        initWidget(uiBinder.createAndBindUi(this));
    }

    @Override
    public HasClickHandlers getUserNameClickHandler() {
        return label;
    }

    @Override
    public void setUserName(String username) {
        label.setText(username);
    }

    @Override
    public Widget asWidget() {
        return this;
    }
}

Avec le fichier TestView.ui.xml associé :

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
    xmlns:g="urn:import:com.google.gwt.user.client.ui">
    <g:Label ui:field="label" />
</ui:UiBinder>

Cette classe étend donc notre Display du présenteur et accède aux composant GWT.

On peux ainsi tester facilement le code de notre présenteur car il ne possède aucune adhérence à GWT.

Nous avons choisi dans un premier temps de se passer de ces framework et d’appliquer les pattern MVP en suivant uniquement les pratiques conseillées par Google. Maintenant que nous avons une première expérience avec le développement MVP, nous pouvons penser à utiliser un de ces framework pour bénéficier de leurs utilitaires comme la gestion de l’historique, du pattern action etc. Si nous devions en choisir un à ce jour, ce serait gwt-platform qui est issu de gwt-presenter et gwt-dispatch.

lundi 24 janvier 2011

Retour d'expérience d'un projet GWT : 4 Architecture Modèle-Vue-Presenteur (4/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Le MVP ou modèle vue présenteur est un modèle de conception qui a les objectifs suivants:

  • Découplage : le découpage en couche permet de construire des applications modulaires compatibles avec les contraintes de dépendances du chargement à la demande
  • Testabilité : Chaque classe destinée a être testée out-of-the-box possède une interface qui permettra de bouchonner certains comportements.
  • Optimisation des appels de service
  • Centralisation des erreurs
  • Gestion de l'historique
  • Anticipation, sur les évolutions du web comme HTML5...

Les 3 concepts du MVP sont :

  • Le modèle, les données de l'application, souvent issues de la base de données, il est destiné à être manipulé par l'UI. Il n'a pas d'adhérence avec les autres classes MVP, il est isolé.
  • La vue, la partie de la page web affichée dans l'interface utilisateur, qui se compose des widgets et des conteneurs de widgets.
  • Le présenteur, le controleur du pattern MVC sauf qu'il rend totalement étanche les échanges entre la vue et le modèle (alors que MVC permet à la vue de référencer directement ses modèles). Il porte leur le comportement de l'application.

MVP

La vue est passive et c'est le présenteur qui s'occupe des traitements et de la logique évenementielle. La vue et le présenteur partagent un contrat grâce à une interface. Pour que le contrôleur puisse être correctement tester avec JUnit sans utiliser la version de GWT qui utilise HtmlUnit pour lancer les tests, il faut que le contrôleur ne possède que du code Java hors code GWT (composant GWT, GWT.create(), Internationalisation ...).

Le MVP s'appuie sur plusieurs briques dont certaines sont facultatives :

  • Un gestionnaire d'évènement ou EventBus qui a pour but de découpler les différentes couches qui communiquent par l'émission d'évènements.
  • Un moteur d'IOC pour faciliter la création et la liaison des différents object. (facultatif)
  • Le pattern action pour simplifier les appels de services et offrir une meilleur testabilité. (facultatif)

Pour comprendre au mieux, le fonctionnement concret du modèle de conception MVP, il faut lire les 2 articles de Google suivants :

Un projet d'exemple est proposé par Google

Schéma de fonctionnement de GWT avec MVP sans UiBinder:

MVP ss UiBinder

Schéma de fonctionnement de GWT avec MVP avec UiBinder:

MVP avec UiBinder

Il existe également d'autres articles expliquant le fonctionnement de MVP :

Il existe un projet d'exemple illustrant le fonctionnement du MVP : http://code.google.com/p/gwt-mvp-sa...

Dans le livre "la programmation GWT2" de Sami Jaber, un article détaille le fonctionnement de MVP en s'appuyant sur une framework existant : http://code.google.com/p/gwt-presenter/

Il existe de nombreux framework MVP pour GWT comme gwt-presenter, gwt-platform ou mvp4g.

Dans GWT 2.1, GWT a introduit un framework MVP, cependant celui-ci décrit le principe.