GWT est un framework permettant de créer une interface Web riche en Java plutôt qu’en HTML et Javascript. La programmation de l’interface ressemble beaucoup à du Swing : new Panel(), new Button(), add ClickListener… C’est une approche assez séduisante : pas besoin de connaître un nouveau langage, possibilité de réutiliser les outils que l’on utilise en Java… De plus, comme tout est en Java (même la partie vue du modèle MVC!), on devrait donc pouvoir faire des tests sur l’IHM. Essayons donc.
Première tentative (en TDD :-) )
Prenons un exemple simple : on veut afficher un champ de saisie texte, un bouton et un label. Quand on clique sur le bouton, le contenu du champ texte saisi par l’utilisateur est placé dans le label. Écrivons d’abord le test correspondant (en JUnit 4) :
1 2 3 4 5 6 7 8 9 10 11 | @Test public void testClick() { GwtExample view = new GwtExample(); Assert.assertNull(view.getLabel().getText()); view.getTextBox().setText("mon text"); // création d'un évènement "clic" basique NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, false, false, false, false); // dispatch de l'évènement DomEvent.fireNativeEvent(event, view.getButton()); Assert.assertEquals("mon text", view.getLabel().getText()); } |
A l’aide de la fonctionnalité « quick fix eclipse » (et d’un peu de connaissance de GWT :-)), on écrit le code de la vue correspondant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public class GwtExample extends Composite { private Label label; private TextBox textBox; private Button button; public GwtExample() { FlowPanel fp = new FlowPanel(); textBox = new TextBox(); fp.add(textBox); button = new Button("valider"); fp.add(button); button.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { label.setText(textBox.getText()); } }); label = new Label("init"); fp.add(label); initWidget(fp); } public Label getLabel() { return label; } public TextBox getTextBox() { return textBox; } public Button getButton() { return button; } } |
Lançons le test JUnit précédent :
1 2 3 4 5 6 7 8 | java.lang.ExceptionInInitializerError ... Caused by: java.lang.UnsupportedOperationException: ERROR: GWT.create() is only usable in client code! It cannot be called, for example, from server code. If you are running a unit test, check that your test case extends GWTTestCase and that GWT.create() is not called from within an initializer or constructor. at com.google.gwt.core.client.GWT.create(GWT.java:85) at com.google.gwt.user.client.ui.UIObject.(UIObject.java:140) ... 23 more |
Qu’est ce que cela signifie ? Que les classes GWT ne sont pas destinées à être utilisées dans une JVM standard, mais fonctionnent seulement une fois compilées en Javascript, et exécutées dans un navigateur. Aïe. Adieu les tests ?
Des solutions existantes
GWTTestCase
GWT fournit une classe pour faire des tests unitaires : GWTTestCase. Cette classe présente un certain nombre de défauts :
- La gestion des évènements natifs n’est implémentée que depuis GWT 1.6.
GWTTestCasene permet pas de tester la partie Vue de manière efficace sur les versions précédentes. - Lenteur. En fait cette classe lance un HostedMode masqué, il lui faut donc plusieurs secondes pour s’initialiser. Cela rend difficile le TDD, même en utilisant
GWTTestSuitepour ne lancer qu’un HostedMode par module GWT testé. - Localisation des fichiers. Les tests unitaires doivent être compilés par GWT. Ils doivent donc être référencés par le fichier
MonApp.gwt.xml. Cela complique pas mal le lancement et le packaging de l’application, surtout avec Maven.
De plus, l’utilisation de GWTTestCase et de GWTTestSuite limite fortement l’accès aux APIs Java : les tests unitaires doivent être compatibles avec le compilateur GWT, qui se charge de compiler les IHM Java en Javascript. Il n’est alors pas possible d’utiliser des librairies de tests telles que Unitils ou Easymock.
La liste des classes Java émulées en JavaScript est disponible ici.
Utilisation d’interfaces
Une solution pour tester GWT est de remplacer tous les objets GWT par des objets mock : des classes qui simulent le comportement des classes GWT, mais qui fonctionnent dans une JVM standard. Cette solution est possible, mais elle a un gros inconvénient : comme les composants GWT ne sont pas des interfaces mais des classes concrètes, il va donc falloir modifier le code de notre application pour qu’elle utilise des interfaces au lieu de ces classes, pour que l’on puisse mocker dans nos tests, comme ici. Cela nous oblige donc à modifier lourdement le code de notre application, et à écrire des mocks pour toutes les classes GWT que l’on va utiliser. On doit pouvoir faire mieux.
Le noeud du problème
Afin d’avancer, nous avons donc regardé pourquoi Google avait bloqué le fonctionnement des classes GWT dans une JVM standard. La raison est simple : une bonne partie du code de ces classes est du code JSNI. Ce code se présente de la manière suivante :
1 | public static native void alert(String msg) /*-{ $wnd.alert(msg); }-*/; |
C’est une fonction native. C’est à dire que lors de l’exécution, la JVM va essayer d’exécuter une fonction ayant le même nom dans une DLL (ou un .so). Quand ces classes sont compilées en Javascript, la méthode est remplacée par le code situé entre /* et */. C’est pourquoi on ne peut les exécuter dans une JVM standard, non Javascript. Voir ici pour plus de détails sur JSNI.
Une autre conséquence est qu’une partie du fonctionnement de GWT est implémenté, non pas en Java, mais en Javascript. Même si on arrive à contourner le problème des méthodes natives, il faudra trouver une solution pour maintenir ce fonctionnement.
Conclusion
Tout cela n’est guère encourageant. N’y a t il donc pas moyen de faire des tests sur notre belle IHM écrite en Java ? La suite au prochain épisode …
Suggestion d'articles :


« comme les composants GWT ne sont pas des interfaces mais des classes concrètes, il va donc falloir modifier le code de notre application pour qu’elle utilise des interfaces au lieu de ces classes, pour que l’on puisse mocker dans nos tests »
EasyMock Class Extension permet de mocker des classes.
Certes, d’ailleurs nous utiliserons intensément Easymock ClassExtension dans la suite. Voici quelques éléments qui rendent difficile l’utilisation « standard » d’Easymock pour les tests :
- Il y a quelques méthodes dans les classes de bases de GWT qui sont taggées final, ce qui empèche de les mocker (par exemple
setElementdansUIObject). Cela complique l’écriture de tests avec des mocks.- Il y a pas mal de méthodes statiques en GWT, certaines très utilisées (
GWT.createpar exemple). Ces méthodes ne sont pas mockables avec Easymock.- La programmation d’une IHM se prête assez mal aux tests avec des mocks. Je m’explique : dans un bean spring, vous injectez d’autres beans. A la place des vrais beans injectés, vous pouvez injecter des mocks pour réaliser les tests, c’est facile et efficace. Dans une IHM GWT, de multiples objets sont créer partout. Par exemple une vue va créer pas mal de widget graphiques. Pour pouvoir les mocker, il va falloir, par exemple, passer par une Factory, et modifier celle-ci pour retourner des mocks au lieu des objets graphiques. Ce qui va alourdir le code de l’IHM.
[...] (en local sur Mac du moins). La solution que nous avons mis en place (ce framework, décrit ici), est donc toujours, à mon avis, pertinente. Nous allons donc porter notre framework de test vers [...]
[...] Écrire un éditeur pour ça serait trop couteux. En revanche, écrire un plugin Eclipse qui permet d’éditer ces fichiers n’est pas très compliqué. L’exemple qui nous servira de support ici est le DSL de tests d’intégration utilisé dans le framework de test GWT-test-utils. [...]
Bonjour,
je dois faire des tests SELENIUM pour un projet développé avec GWT et j’ai un prb avec la balise la fonction clik n a aucun effet sur le bouton. et si je lance mon test selenium le bouton ne marche plus même avec un click de la souri, je dois fermer selenium pour que ca marche.
Est-ce que ca vous parle tt ca?