uni.sherbrooke 2015 créez la meilleur application grâce à gwt, gwtp et jukito
TRANSCRIPT
MEILLEURES PRATIQUES DE DÉVELOPPEMENT GRÂCE À GWT, GWTP ET JUKITOCHRISTOPHER VIELCHRISTIAN GOUDREAU
Département de Génie InformatiqueUniversité de Sherbrooke Juin 2015
Christopher Viel
Lead DéveloppeurArcbees
+ChristopherVielArcbees
Christian Goudreau
BEOArcbees
+ChristianGoudreau@imchrisgoudreau
Partie 1
Présentation d’Arcbees
Partie 2
Résumé du MVP Model-View-PresenterPartie 3
Survol de GWTP Framework MVP pour GWTPartie 4
Communication serveur Rest-Dispatch
Slides disponibles à https://goo.gl/PBzmwL
Partie 5
Tests unitaires Exemples avec Mockito et JukitoPartie 6
Meilleures pratiquesPartie 7
GWT 3.0
Slides disponibles à https://goo.gl/PBzmwL
Partie 1
ArcbeesArchitectes de la Simplicité
Partie 1 Arcbees
Partie 1 Arcbees
Notremission
Créer des produits permettant à nos clients, nos abeilles et la communauté de développeurs d'optimiser leurs processus, habitudes et façons de penser,en créant continuellement de la valeur et en transformant la façon dont ils travaillent.
Partie 1 Arcbees
Originede Arcbees
Afin d'améliorer leur efficacité de production, les deux cofondateurs ont conçu GWT-Platform, un outil open source de création d'applications web doté d'une incroyable capacité d'adaptation accélérant la vitesse de développement des applications.
Partie 1 Arcbees
Évolutionrapide
Rapidement, GWT-Platform a généré une importante demande de soutien de la part de ses utilisateurs et les deux entrepreneurs n'ont eu d'autres choix que de créer officiellement Arcbees le 16 juillet 2010 afin de répondre à ces requêtes.
Partie 1 Arcbees
Reconnaissance
de Google
En moins d'un an, Google a remarqué la jeune entreprise qui a été invitée à donner une conférence au Google I/O, puis, en 2012, Christian a été nommé sur le Comité de direction de GWT, groupe en charge de la direction et de l'évolution du framework.
Partie 1 Arcbees
Partie 1 Arcbees
Partie 2
MVPModel View Presenter
➢ Model : Définit les données qui seront ultimement gérées dans l’interface utilisateur.
➢ View : Affiche le modèle pour l’utilisateur. Généralement passive: seulement de la logique d’affichage. Délègue les interactions utilisateur (events) au presenter.
➢ Presenter : Récupère et met à jour le modèle. Interprète le modèle les données pour les transférer à la vue.
Partie 2 MVP
MVP
Caractéristiquesdu MVP
➢ Couplage faible entre les différentes couches.
➢ Flot très simple, presque linéaire.
➢ Communique exclusivement au moyen d’interfaces.
➢ Vue passive. Ne prend aucune décision et notifie le presenter.
Partie 2 MVP
Partie 2 MVP
MVP
Partie 2 MVP
MVCcomparéà MVP
Quand l’utiliser➢ Idéal lorsqu’un interface utilisateur est
nécessaire pour contrôler l’application.➢ Pratique lorsqu’il y a beaucoup d’édition
de données (formulaires, CRUD, etc.)➢ Simple à mettre en place lorsqu’une
communication entre client - serveurest requise.
Partie 2 MVP
Quandl’utiliser
Quand l’utiliser
➢ Simple à tester : Le faible couplage permet de bien isoler les couches.
➢ Réutilisation de code plus aisée : Le flot plus simple et l’utilisation intensive d’interfaces permet de mieux extraire le code réutilisable.
Partie 2 MVP
Les bénéfices et les désavantages
Quand l’utiliser
➢ Flexible : Il est facile de remplacer n’importe quel composant tant qu’on respecte le contrat défini par les interfaces.
➢ Peut se combiner à d’autres design patterns.
Partie 2 MVP
Les bénéfices et les désavantages
➢ Le grand nombre d’interface à gérer augmente la complexité du code. Les IDEs modernes aident néanmoins à atténuer ce problème (détection des implémentations).
➢ La vue passive introduit beaucoup de code boilerplate: beaucoup d’aller-retour entre le presenter et la vue.
Partie 2 MVP
Les bénéfices et les désavantages
Partie 3
Survol de GWTPFramework MVP pour GWT
➢ Repose sur GWT et GIN (GWT INjection).➢ Abstraire la gestion du pattern MVP,
mais aussi imposer son utilisation.➢ Abstraire la gestion cliente: initialiser l’
application, navigation, communication avec le serveur.
➢ Réduire le code à écrire habituellement requis en GWT.
➢ Optimisations: Proxy, code splitting...
GWTP
Partie 3 GWTP
➢ Permet un découplage plus poussé et rend le code plus modulaire (DIP, IoC). ○ Aide à atteindre de meilleures
pratiques,○ Classes immutables,○ Créer de nouvelles dépendances
devient plus simple : Extraction de code vers des collaborateurs (SRP).
➢ Tend à rendre les tests plus simples: Jukito, GuiceBerry…
➢ En bref : Aide à atteindre les principes SOLID
Injectionde dépendances
Partie 3 GWTP
➢ Limiter le code redondant à écrire qui est habituellement requis en GWT.
➢ Séparer les phases d’initialisation en phases logiques simple à étendre ou remplacer.
➢ Permet d’utiliser l’injection de dépendances plus tôt dans le programme.
➢ Configuration initiale à un seul endroit.
Références : DefaultModule, PreBootstrapper, Bootstrapper
Initialisationde l’application
Partie 3 GWTP
Exemple: Initialisation et Bootstrapper
Configuration via le module GWT
public class ClientModule extends AbstractGinModule { @Override protected void configure() { install(new DefaultModule.Builder() .defaultPlace(NameTokens.LOGIN) .errorPlace(NameTokens.LOGIN) .unauthorizedPlace(NameTokens.UNAUTHORIZED) .build());
install(new ApplicationModule());
bind(ResourceLoader.class).asEagerSingleton(); }}
<?xml version="1.0" encoding="UTF-8"?><module> <inherits name="com.gwtplatform.mvp.MvpWithEntryPoint"/>
<source path="client"/> <source path="shared"/>
<set-configuration-property name="gwtp.bootstrapper" value="com.arcbees.demo.client.DefaultBootstrapper"/> <extend-configuration-property name="gin.ginjector.modules" value="com.arcbees.demo.client.ClientModule"/></module>
Bootstrapper (facultatif!)
Configuration via GIN
public class DefaultBootstrapper implements Bootstrapper { private final PlaceManager placeManager;
@Inject DefaultBootstrapper(PlaceManager placeManager) { this.placeManager = placeManager; }
@Override public void onBootstrap() { // Initialize states then reveal the app: placeManager.getCurrentPlaceRequest(); }}
➢ Permet de réagir à différents stimuli provenant d’ailleurs dans l’application.
➢ Permet d’éviter de coupler des classes qui n’ont aucun point commun.
➢ Implémentation générique et de l’Observer.
Références : addRegisteredHandler, addVisibleHandler, @ProxyEvent
Plus de détails: https://blog.arcbees.com/?p=699
Évenements(GWT, EventBus)
Partie 3 GWTP
➢ Isoler le comportement du presenter pour chaque phase de sa vie.
➢ Comportements isolés donc plus simple à tester unitairement.
Lifecycle(GWTP)
Références : onBind, onReveal, onReset, onHide, onUnbind, prepareFromRequest
Partie 3 GWTP
Exemple: Aperçu des méthodes du lifecyclepublic class EditManufacturerPresenter extends PresenterWidget<MyView> { interface MyView extends View { }
@Inject EditManufacturerPresenter( EventBus eventBus, MyView view) { super(eventBus, view); }
@Override protected void onBind() {}
@Override protected void onReveal() {}
@Override protected void onReset() {}
@Override protected void onHide() {}
@Override protected void onUnbind() {}}
➢ Gère la navigation entre les places. Beaucoup de code à écrire autrement.
➢ Offre plusieurs façons de présenter le token : le hash dans l’URI, après le #.
➢ Possibilité de rediriger vers une même page lorsqu’un token inconnu est accédé. Page 404 à-la GWTP.
Références : PlaceManager, PlaceRequest, TokenFormatter
Navigation(GWTP)
Partie 3 GWTP
Exemple: Utiliser le Place Manager
Révéler un name tokenprivate final PlaceManager placeManager;
@InjectEditManufacturerPresenter( EventBus eventBus, MyView view, PlaceManager placeManager) { super(eventBus, view);
this.placeManager = placeManager;}
@Overridepublic void cancel() { PlaceRequest placeRequest = new PlaceRequest.Builder() .nameToken(NameTokens.MANUFACTURERS) .build(); placeManager.revealPlace(placeRequest);}
Récupérer le name token courantprivate final PlaceManager placeManager;
@InjectHeaderPresenter( EventBus eventBus, MyView view, PlaceManager placeManager) { super(eventBus, view);
this.placeManager = placeManager;}
@Overrideprotected void onReset() { PlaceRequest currentPlace = placeManager.getCurrentPlaceRequest(); String currentNameToken = currentPlace.getNameToken();
getView().setActiveMenuItem(currentNameToken);}
➢ Permet de configurer et restreindre l’accès à certains tokens.
➢ Possibilité de configurer un gatekeeper par défaut.
➢ Permet de rediriger vers une même page lorsque qu’un accès non autorisé est détecté. Page 401 à-la GWTP.
➢ Attention: Si la page protégée par un gatekeeper accède à des ressources serveur, le serveur doit lui aussi protéger ses ressources!
Références : Gatekeeper, GatekeeperWithParams, @UseGatekeeper, @GatekeeperParams, @NoGatekeeper, @DefaultGatekeeper
Sécurité(GWTP)
Partie 3 GWTP
Exemple: Utiliser un Gatekeeper
Gatekeeperpublic abstract class AclGatekeeper implements GatekeeperWithParams { private final CurrentSession currentSession;
private Set<String> permissions;
@Inject AclGatekeeper(CurrentSession currentSession) { this.currentSession = currentSession; this.permissions = new HashSet<>(); }
@Override public GatekeeperWithParams withParams(String[] params) { permissions = new HashSet<>(Arrays.asList(params)); return this; }
@Override public boolean canReveal() { return currentSession.isLoggedIn() && currentSession.hasPermissions(permissions); }}
Utilisation du Gatekeeperpublic class EditGroupMembersPresenter extends Presenter<MyView, MyProxy> { interface MyView extends View { }
@ProxyStandard @NameToken(NameTokens.EDIT_GROUP_MEMBERS) @UseGatekeeper(AclGatekeeper.class) @GatekeeperParams({"GROUPS_UPDATE", "MEMBERS_UPDATE"}) interface MyProxy extends ProxyPlace<EditGroupMembersPresenter> { }
/* snip */}
➢ Le coeur des fonctions de GWTP :○ Accès au lifecycle,○ Gestion centralisée des événements○ Possède une vue,○ Communication vue vers presenter via
un UiHandlers.➢ Permet de développer
des composants réutilisableset testables.
➢ Permet de définir des Slots pour imbriquer d’autres PresenterWidgets.
Références : PresenterWidget, UiHandlers, View, OrderedSlot, SingleSlot, Slot
PresenterWidgets
Partie 3 GWTP
Exemple: Un PresenterWidget
Le PresenterWidgetpublic class DashboardPresenter extends PresenterWidget<MyView> { interface MyView extends View { }
static final Slot<PresenterWidget<?>> SLOT_TEMPLATE = new SingleSlot<>();
private final Presenter<?, ?> presenter;
@Inject DashboardPresenter( EventBus eventBus, MyView view, Presenter<?, ?> presenter) { super(eventBus, view);
this.presenter = presenter; }
@Override protected void onBind() { setInSlot(SLOT_TEMPLATE, presenter); }}
Sa vuepublic class DashboardView extends ViewImpl implements MyView { interface Binder extends UiBinder<Widget, DashboardView> { }
@UiField SimplePanel template;
@Inject DashboardView(Binder uiBinder) { initWidget(uiBinder.createAndBindUi(this));
bindSlot(DashboardPresenter.SLOT_TEMPLATE, template); }}
Sa configuration GINpublic class DashboardModule extends AbstractPresenterModule { @Override protected void configure() { bindPresenterWidget(DashboardPresenter.class, DashboardPresenter.MyView.class, DashboardView.class); }}
➢ Un PresenterWidget spécialisé.➢ Doit être associé à un proxy.➢ Initialisation uniquement lorsque
nécessaire: améliore le chargement initial.➢ Peut être associé à un name token (place).➢ Permet de définir des NestedSlots
pour imbriquer d’autres Presenters sans couplage.
Références : Presenter, NestedSlot, Proxy, ProxyPlace, @ProxyStandard, @NameToken
Presenter
Partie 3 GWTP
Exemple: Un Presenter
Le PresenterWidgetpublic class DashboardPresenter extends Presenter<MyView, MyProxy> { interface MyView extends View { }
@ProxyStandard @NameToken(NameTokens.DASHBOARD) @UseGatekeeper(LoggedInGatekeeper.class) interface MyProxy extends ProxyPlace<DashboardPresenter> { }
static final NestedSlot SLOT_TEMPLATE = new NestedSlot();
@Inject DashboardPresenter( EventBus eventBus, MyView view, MyProxy proxy) { super(eventBus, view, proxy, ApplicationPresenter.SLOT_MAIN); }}
public class DashboardView extends ViewImpl implements MyView { interface Binder extends UiBinder<Widget, DashboardView> { }
@UiField SimplePanel template;
@Inject DashboardView(Binder uiBinder) { initWidget(uiBinder.createAndBindUi(this));
bindSlot(DashboardPresenter.SLOT_TEMPLATE, template); }}
Sa configuration GINpublic class DashboardModule extends AbstractPresenterModule { @Override protected void configure() { bindPresenter(DashboardPresenter.class, DashboardPresenter.MyView.class, DashboardView.class, DashboardPresenter.MyProxy.class); }}
Sa vue
➢ Un PresenterWidget ayant un popup comme vue.
➢ Les mêmes avantages qu’un PresenterWidget: slots, lifecycle.
➢ Délègue l’affichage / masquage du popup au presenter.
➢ Abstrait le concept de positionnement du popup.
Références : PopupView, PopupViewImpl, addToPopupSlot, PopupPositioner
Popups
Partie 3 GWTP
Comunication ServeurRest-Dispatch
Partie 4
Partie 4 Rest-Dispatch
Amené à disparaître dans GWT. Nécessite beaucoup de code pour un cas simple. Renforcit le couplage entre client et serveur. Plus simple de conserver le code client et serveur compatible.
RPC REST
Permet de communiquer avec n’importe quel API REST. Presque tous les serveurs permettent de faire du REST : à la mode. Plus facile de modifier l’API et oublier de mettre le code client à jour.
➢ Implémentation partielle de JSR-311 1.1 (JAX-RS).
➢ Définition d’interface et configuration de l’API au moyen d’annotations.
➢ Code client (GWT) seulement. L’API doit être codé en utilisant une autre technologie.
➢ Possibilité de créer ses propres extensions.
Rest-Dispatch
Partie 4 Rest-Dispatch
➢ Usage plus explicite que la syntaxe de base.
➢ Les interfaces et annotations peuvent être réutilisé sur l’implémentation serveur (DRY).
➢ Plus simple de conserver un API privé compatible à la fois sur le client et le serveur.
➢ Désavantage: Perte du type safety lors de la création de callbacks.
Extension :Delegates
Partie 4 Rest-Dispatch
Exemple: Configurer et utiliser Rest-Dispatch
Le module GWT Les paths
Le module GIN L’interface de ressource
<?xml version='1.0' encoding='UTF-8'?><module> <inherits name="com.gwtplatform.dispatch.rest.delegates.ResourceDelegate"/> <inherits name='com.gwtplatform.mvp.MvpWithEntryPoint'/>
<source path="client"/></module>
public class ResourcesModule extends AbstractGinModule { @Override protected void configure() { install(new RestDispatchAsyncModule.Builder() /* Additional configurations */ .build()); }
@Provides @RestApplicationPath String applicationPath() { String baseUrl = GWT.getHostPageBaseURL(); if (baseUrl.endsWith("/")) baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
return baseUrl + API; }}
public static class DashboardPaths { public static final String DASHBOARD_ID = "dashboard-id"; public static final String DASHBOARDS = "/dashboards"; public static final String DASHBOARD = "/{" + DASHBOARD_ID + "}";}
@Path(DASHBOARDS)public interface DashboardResource { @GET List<Dashboard> getAll(); @GET @Path(DASHBOARD) Dashboard get(@PathParam(DASHBOARD_ID) int id);
@PUT @Path(DASHBOARD) void update(@PathParam(DASHBOARD_ID) int id, Dashboard dashboard);}
Exemple: Configurer et utiliser Rest-Dispatch (suite)
Utilisation de la ressourcestatic final SingleSlot<PresenterWidget<?>> SLOT_TEMPLATE = new SingleSlot<>();
private final ResourceDelegate<DashboardResource> dashboards;private final DashboardTemplateFactory templateSelector;
@InjectDashboardPresenter( EventBus eventBus, MyView view, MyProxy proxy, ResourceDelegate<DashboardResource> dashboards, DashboardTemplateFactory templateFactory) { super(eventBus, view, proxy, ApplicationPresenter.SLOT_MAIN);
this.dashboards = dashboardService; this.templateFactory = templateFactory;}
@Overrideprotected void onReveal() { dashboards.withCallback(new AbstractAsyncCallback<Dashboard>() { @Override public void onSuccess(Dashboard result) { PresenterWidget<?> template = templateFactory.createTemplate(result); setInSlot(SLOT_TEMPLATE, template); } }).get(DashboardSettings.DEFAULT_DASHBOARD_ID);}
Tests unitairesTester GWTP et Rest-Dispatch avec Mockito et Jukito
Partie 5
Partie 5 Tests unitaires
Jukito
Jukito permet à vos tests d’utiliser l’injection de dépendances. Peu importe le type de test — unitaire, intégration ou quoi que ce soit de loufoque —, le code redondant dû au mocking diminuera grandement. Rapidement, vous ne pourrez plus vous passez de sa syntaxe sécuritaire et basée sur les annotations!
➢ Mocking automatique➢ Injection des mocks➢ Annotation @ALL➢ Léger➢ S'adapte à toutes tailles de test➢ Facile d’installation
Fonctionalités
Partie 5 Tests unitaires
ExemplesSur GitHub
Mockito et Jukito:ManufacturerDetailPresenter: https://goo.gl/27NsKL
ManufacturerDetailPresenterTest: https://goo.gl/irq5rV
ManufacturerDetailPresenterMockitoTest: https://goo.gl/EIlj47
Jukito @All:PersonRenderer: https://goo.gl/o4UGZn
PersonRendererTest: https://goo.gl/uhcL2k
Partie 5 Tests unitaires
Mockito : Initialisation
CAS DE TEST TEST@InjectManufacturerDetailPresenter( EventBus eventBus, MyView view, MyProxy proxy, ResourceDelegate<ManufacturersResource> manufacturers, PlaceManager placeManager, EditManufacturerMessages messages) { super(eventBus, view, proxy, SLOT_MAIN_CONTENT);
this.manufacturers = manufacturers; this.placeManager = placeManager; this.messages = messages;
getView().setUiHandlers(this);}
public class ManufacturerDetailPresenterMockitoTest { // SUT private ManufacturerDetailPresenter presenter;
// Mocks (created by Mockito) @Mock private EventBus eventBus; @Mock private MyView view; @Mock private MyProxy proxy; @Mock private ResourceDelegate<ManufacturersResource> manufacturers; @Mock private PlaceManager placeManager;
@Before public void setUp() { // Create @Mock and @Captor fields. MockitoAnnotations.initMocks(this);
// Manual way to create a mock EditManufacturerMessages messages = mock(EditManufacturerMessages.class);
// Manual creation of the SUT. presenter = new ManufacturerDetailPresenter(eventBus, view, proxy, manufacturers, placeManager, messages); }
Mockito : Test
CAS DE TEST TEST@Overrideprotected void onReveal() { List<ActionType> actions; if (createNew) { actions = Arrays.asList(ActionType.DONE); } else { actions = Arrays.asList(ActionType.DELETE, ActionType.UPDATE); }
ChangeActionBarEvent.fire(this, actions, false);}
@Testpublic void onReveal() { // when presenter.onReveal();
// then // Manual way to create an argument captor ArgumentCaptor<ChangeActionBarEvent> captor = ArgumentCaptor.forClass(ChangeActionBarEvent.class);
verify(eventBus) .fireEventFromSource(captor.capture(), same(presenter));
ChangeActionBarEvent event = captor.getValue(); assertThat(event).isNotNull(); assertThat(event.getActions()).containsOnly(ActionType.DONE); assertThat(event.getTabsVisible()).isFalse();}
Jukito : Initialisation
CAS DE TEST TEST
@RunWith(JukitoRunner.class)public class ManufacturerDetailPresenterTest { // SUT (Injected by Jukito). We don't need to create all dependencies explicitly, Jukito will mock them. @Inject private ManufacturerDetailPresenter presenter;
// Mocks (injected by Jukito) @Inject private EventBus eventBus;
// Captors (Create by Mockito) @Captor private ArgumentCaptor<ChangeActionBarEvent> changeActionBarEventCaptor;
@Before public void setUp() { // Create @Mock and @Captor fields. MockitoAnnotations.initMocks(this); }
@InjectManufacturerDetailPresenter( EventBus eventBus, MyView view, MyProxy proxy, ResourceDelegate<ManufacturersResource> manufacturers, PlaceManager placeManager, EditManufacturerMessages messages) { super(eventBus, view, proxy, SLOT_MAIN_CONTENT);
this.manufacturers = manufacturers; this.placeManager = placeManager; this.messages = messages;
getView().setUiHandlers(this);}
Jukito : Test
CAS DE TEST TESTJukito: Test
@Overrideprotected void onReveal() { List<ActionType> actions; if (createNew) { actions = Arrays.asList(ActionType.DONE); } else { actions = Arrays.asList(ActionType.DELETE, ActionType.UPDATE); }
ChangeActionBarEvent.fire(this, actions, false);}
@Testpublic void onReveal_newManufacturer_preparesActionBar() { // when presenter.onReveal();
// then verify(eventBus) .fireEventFromSource(changeActionBarEventCaptor.capture(), same(presenter));
ChangeActionBarEvent event = changeActionBarEventCaptor.getValue(); assertThat(event).isNotNull(); assertThat(event.getActions()).containsOnly(ActionType.DONE); assertThat(event.getTabsVisible()).isFalse();}
Jukito : Utiliser @All pour tester plusieurs cas
CAS DE TEST TEST@InjectPersonRenderer( Messages messages, @Assisted Mode mode) { this.mode = mode; this.messages = messages;}
public String render(Person person) { String result = "";
if (person != null) { String firstName = person.getFirstName(); String middleName = person.getMiddleName(); String lastName = person.getLastName(); String title = renderTitle(person);
if (mode.isDisplayTitle() && !Strings.isNullOrEmpty(title)) { result += title + " "; } if (mode.isDisplayFirstName() && !Strings.isNullOrEmpty(firstName)) { result += firstName + " "; } if (mode.isDisplayMiddleName() && !Strings.isNullOrEmpty(middleName)) { result += middleName + " "; } if (mode.isDisplayLastName() && !Strings.isNullOrEmpty(lastName)) { result += lastName; } }
if (result.isEmpty()) { result = messages.unknown(); }
return result;}
public class PersonNameTestCase { private final Person person;
private Mode mode; private String expected;
public PersonNameTestCase() { person = null; }
public PersonNameTestCase( String firstName, String lastName) { person = new Person(firstName, lastName); }
/* Setters */
public Person getPerson() { return person; }
public Mode getMode() { return mode; }
public String getExpected() { return expected; }}
Jukito : Utiliser @All pour tester plusieurs cas (suite)
TEST TEST@RunWith(JukitoRunner.class)public class PersonRendererTest { public static class Module extends JukitoModule { @Override protected void configureTest() { bindManyInstances(PersonNameTestCase.class, new PersonNameTestCase("Zom", "Bee") .mode(Mode.SHORT).expected("Zom Bee"), new PersonNameTestCase("Zom", "Bee").title(Title.MR) .mode(Mode.SHORT).expected("Zom Bee"), new PersonNameTestCase("Zom", "Bee").middleName("Buzz") .mode(Mode.SHORT).expected("Zom Bee"), new PersonNameTestCase("Zom", "Bee").title(Title.MR).middleName("Buzz") .mode(Mode.SHORT).expected("Zom Bee"),
new PersonNameTestCase("Zom", "Bee") .mode(Mode.FORMAL).expected("Zom Bee"), new PersonNameTestCase("Zom", "Bee").title(Title.MS) .mode(Mode.FORMAL).expected("Ms. Zom Bee"), new PersonNameTestCase("Zom", "Bee").middleName("Buzz") .mode(Mode.FORMAL).expected("Zom Buzz Bee"), new PersonNameTestCase("Zom", "Bee").title(Title.MS).middleName("Buzz") .mode(Mode.FORMAL).expected("Ms. Zom Buzz Bee"),
new PersonNameTestCase() .mode(Mode.SHORT).expected(UNKNOWN), new PersonNameTestCase() .mode(Mode.FORMAL).expected(UNKNOWN) ); } }
private static final String UNKNOWN = "unknown";
@Injectprivate Messages messages;
@Beforepublic void setUp() { given(messages.unknown()).willReturn(UNKNOWN); given(messages.title(Title.MR)).willReturn("Mr."); given(messages.title(Title.MS)).willReturn("Ms.");}
@Testpublic void render(@All PersonNameTestCase testCase) { // given Person person = testCase.getPerson(); Mode mode = testCase.getMode(); PersonRenderer renderer = new PersonRenderer(messages, mode);
// when String result = renderer.render(person);
// then String expected = testCase.getExpected();
assertThat(result).overridingErrorMessage( "Expected %s with mode %s to be '%s'.", person, mode, expected) .isEqualTo(expected);}
Jukito : Utilitaires pour tester Rest-Dispatch
CAS DE TEST TESTpublic void onSave(ManufacturerDto manufacturer) { manufacturersDelegate .withCallback(new AbstractAsyncCallback<ManufacturerDto>(this) { @Override public void onSuccess(ManufacturerDto newManufacturer) { DisplayMessageEvent.fire(ManufacturerDetailPresenter.this, new Message(messages.manufacturerSaved(), SUCCESS));
PlaceRequest placeRequest = new Builder() .nameToken(NameTokens.MANUFACTURER) .build(); placeManager.revealPlace(placeRequest); } }) .saveOrCreate(manufacturerDto);}
@Testpublic void onSave_showsMessage_revealsManufacturers( EditManufacturerMessages messages) { // given ManufacturerDto manufacturer = new ManufacturerDto(); ManufacturerDto resultManufacturer = new ManufacturerDto();
givenDelegate(manufacturersResource) .succeed().withResult(resultManufacturer) .when().saveOrCreate(same(manufacturer));
given(messages.manufacturerSaved()).willReturn(A_MESSAGE);
// when presenter.onSave(manufacturer);
// then // note `isA` is used instead of `any`. This is because the event bus // accepts all `GwtEvent` subclasses. `isA` also verifies the type verify(eventBus) .fireEventFromSource(isA(DisplayMessageEvent.class),same(presenter)); verify(placeManager).revealPlace(placeRequestCaptor.capture());
PlaceRequest placeRequest = placeRequestCaptor.getValue(); assertThat(placeRequest.getNameToken()) .isEqualTo(NameTokens.MANUFACTURER);}
GWTMeilleures Pratiques
Partie 6
Avoid widgetsas much as you can
BEST PRACTICE 1
Partie 6
WIDGETSARE HEAVY.
BECAUSE
Partie 6
TAKE CONTROL OF YOUR HTML!
Full Event Mechanism
BP 1 : AVOID WIDGETS AS MUCH AS YOU CAN
Partie 6
Even if you don’t need itFull Event Mechanism
BP 1 : AVOID WIDGETS AS MUCH AS YOU CAN
Partie 6
How to attachevent handlerto elements ?
BP 1 : AVOID WIDGETS AS MUCH AS YOU CAN
Partie 6
Use widget
To encapsulate complex component to reuse
» prefer PresenterWidget if the componenthas a lot of business logic
» In the futur: Web Component.
BP 1 : AVOID WIDGETS AS MUCH AS YOU CAN
Partie 6
Cell widgets (CellTable, CellList…)
HtmlPanelExceptions
BP 1 : AVOID WIDGETS AS MUCH AS YOU CAN
Partie 6
Use anEvent Bus
BEST PRACTICE 2
Partie 6
Use DependencyInjection
BEST PRACTICE 3
Partie 6
Use MVP Pattern
BEST PRACTICE 4
Partie 6
Make Navigation Easy
BEST PRACTICE 5
Partie 6
Use directly your name tokens
BP 5 : MAKE NAVIGATION EASY
Partie 6
Use CSSas much as you can
BEST PRACTICE 6
Partie 6
Loas everythingyou can in the html page
BEST PRACTICE 7
Partie 6
UNITTESTS
Partie 6
Test each protected and publicmethods of your presenters.
UNIT TESTS
Partie 6
CODEREVIEW
Partie 6
Improve qualityof your code
CODE REVIEW
Partie 6
Detect Bugsearlier
CODE REVIEW
Partie 6
A way to learnfrom your peers
CODE REVIEW
Partie 6
Web basedcode review system
CODE REVIEW
Partie 6
CODE REVIEWBest practices
Partie 6
TAKE CONTROL OF YOUR HTML!USE A STYLE GUIDE !
Address/fixall commentsbefore you merge.
CODE REVIEW Best Practice
Partie 6
CONTINUOUSINTEGRATION
Partie 6
Partie 6
The first to do code review is your CI Server
CONTINUOUS INTEGRATION
Partie 6
Partie 6
EnforceCheck Style
CONTINUOUS INTEGRATION
Partie 6
ACTORS
Partie 6
Partie 6
Partie 6
Designer
Partie 6
Web Integrators
Partie 6
Developpers
Partie 6
Partie 6
Partie 6
Partie 6
Partie 6
SUCCESS
Partie 6
GWT 3.0
Partie 7
Java 8 Support
Partie 7
JSInterop
Partie 7
Export any Java codeto Javascript
JSinterop
Partie 7
Export any Javascript APIto Java
Partie 7
JSinterop
Subclassing JavaScript Objects
Partie 7
JSinterop
Web component Material design in GWT
Partie 7
JSinterop
Start toexperimentwith GWT 2.7
» Turn it on with a flag
-XjsInteropMode
» Subject to change !
Partie 7
JSinterop
MERCI
QUESTIONS ?