jeudi 27 novembre 2014

La Loi de Dietzler

La Loi de Dietzler, énoncée par Neal Ford lors de sa conférence “Abstraction Distraction” ( et qui n’est pas sans rappeler le Principe de Pareto), évoque une loi empirique selon laquelle un outil informatique présentant une abstraction élevée ne pourra jamais répondre à 100% des besoins de l’utilisateur.

Cette loi a été initialement rapportée par Terry Dietzler, un collègue de Neal Ford, qui travaillait alors sur des projets Access. Neal Ford l’étend à tous les Langages de 4ème génération.

 Il définit trois catégories :

  •  80% des besoins seront rapides et facile à créer. 
  • 10% des besoins seront possibles à créer mais nécessiteront d’adapter l’outil, le contourner, le “tordre”. 
  • 10% des besoins restants seront impossibles à créer car l’utilisateur sera emprisonné par une abstraction trop élevée. 
 Si un outil est prévu pour un cadre trop idéal et présente un niveau d’abstraction trop élevé sans offrir de possibilités de sortir de ce cadre, alors l’utilisateur va se sentir frustré de ne pas pouvoir en tirer parti intégralement et va, à terme le délaisser.

 Selon Ford, les L4G suivent ce cadre : ils permettent d’implémenter très rapidement des problématiques générales, mais présentent trop d’abstractions ce qui rend compliqué - voire impossible - l’implémentation des cas particuliers. En pratique, on se rend compte qu’aucun cadre n’est idéal et que beaucoup de développeurs - si ce n'est tous - ont besoin d’implémenter des cas particuliers.

 Ce que l’on peut tirer de cette loi, c’est que si l’on souhaite mettre en place un outil offrant un niveau d’abstraction élevé, il faut toujours laisser à l’utilisateur la possibilité d’accéder à la couche de plus bas niveau (sous cette abstraction), pour qu’il puisse prendre en compte les exceptions représentées par les cas particuliers à son contexte.

samedi 4 octobre 2014

Jenkins et Scritpler : comprendre l'execution en mode distribué

Pour la mise en place de jobs de construction un peu tordu, la définition dans Jenkins est vite compliquée à gérer.
Il est plus judicieux de créer de scripter son job dans ce cas. Groovy est un intéressant, notamment pour les développeurs issus du monde Java.

Scriptler permet d’exécuter des scripts Grrovy sur Jenkins. Il est intéressant sur plusieurs points :

  • Permet de centraliser les scripts à un même endroit. Facilite la maintenance et la réutilisabilité
  • Les scripts sont paramétrables.
  • les scripts sont commités sur un repo Git local accessible via http://MON_SERVER_JENKINS/scritpler.git à chaque modification. C’est idéal pour pouvoir par la suite pusher vers d’autres repos et assurer ainsi leur sauvegarde. Dommage pour ce point il n’est pas possible de créer de hooks pour pusher automatiquement vers un repository remote, car JGit (le moteur Git de Jenkins) ne prends pas en compte les hooks. Il faudra donc trouver une autre solution pour pusher les commits vers un repo remote (via un Job Jenkins par exemple)
  • offre la possibilité de gérer l’exécution sur le master ou le slave. C’est sur ce point qu’il est utile de connaitre quelques subtilités.
  • Le job Jenkins execute les scripts Groovy de scripter en les référencant dans un build Step.

Scriptler possède deux systèmes d’exécution de scripts, pas ou peu documenté, voici les subtilités à connaitre lors d’un exécution en mode distribué :

Execution sur Master

Appelé “Restriction - Script is always executed on Master“. Dans ce mode, le script s’exécute sur le master dans la même VM que l’exécution du Job :

  • On dispose de l’API de Jenkins pour interagir avec les informations de build (par exemple, renommer un build).
  • On dispose des logs de construction.
  • On dispose des variables d’environnements du build dispo via http://MON_SERVEUR_JENKINS/env-vars.html/?.
    Attention : certaines variables sont relatives à l’exécuteur. Par exemple, $WORKSPACE indique le chemin du workspace sur l’exécuteur et donc, en mode distribué, sur le slave d’exécution.
  • Si l’exécuteur est un slave, le workspace - et donc les sources - ne sont pas accessibles.

Execution sur Slave

Type de script par défaut. Dans ce mode, le script s’exécute sur la même machine que l’exécuteur Jenkins (donc le slave) dans une VM différente. Il s’agit à un mode equivalent à l’execution de script via une commande groovy externe, hors Jenkins.

  • On ne dispose pas de l’API Jenkins.
  • On ne dispose des variables d’environnements du build. Il faudra passer en paramètres du scripts les variables dont on a besoin.
  • On dispose du workspace et donc, de toutes les ressources checkoutées, construites, etc.
  • On n’as pas accès aux logs construction.

En mode distribué, on se rend compte donc de certaines limitations.
un exemple concret :
Lire une information d’un fichier du workspace pour alimenter une description de build n’est pas faisable en mode distribué. Les sources du workspace sont sur le slave, et l’API de modification de description de build n’y est pas disponible (elle est disponible seulement sur le master). Mon billet sur “Afficher la révision comme numéro de build” n’est donc pas fonctionnel en mode distribué… :-(

A suivre…

mercredi 1 octobre 2014

Retour de DroidCon 2014

Pour cette seconde édition française, la conférence a eu lieu le 22 et 23 Septembre 2014 à Paris. 500 personnes étaient présentes. Trois thèmes sont abordés :

  • Android Everywhere
  • UI/UX
  • Android Development

Je suis loin d’être exhaustif, mais voici un rapide compte-rendu de ce que j’ai vu voir et ressentir de cette conférence.

Android Everywhere

Evidemment le gadget de cette année, c’était la montre connectée. Google ne pouvait évidemment pas reprendre Android tel quel car l’interface est trop petite et doit être adaptée : d’ou l’arrivée d’Android Wear : l’OS spécialisé pour les Wearables.

D’un point de vue ergonomie, on réduit l’approche disruptive de l’utilisation du téléphone (on checke son téléphone, pour vérifier si on a pas de messages, d’appels manqués, tout ça pendant qu’on parle à des amis). L’idée est donc de checker l’information plus souvent, plus rapidement, mais surtout que ce soit l’information qui arrive à l’utilisateur par rapport à son contexte : “show me the information before I even know I need it”. L’information est ensuite oubliée - approche “Fire and Forget”.

Concernant l’ergonomie, on est principalement sur des interactions minimalistes switch/tap. Pour les commandes plus avancées, on utilise la commande vocale “OK Google”.
La montre sous AndroidWear est un objet connecté, certes, mais surtout un objet “compagnon”. Elle a besoin d’un device maitre pour interagir. Dans le cas de gestion de la commande vocale, la montre se connecte au téléphone, puis le téléphone envoi la commande vocale aux serveurs de Google qui analysent puis redescendent l’information vers le téléphone, puis vers la montre.

On a donc 2 types d’applications :

  • Les notifications : toutes les applications qui apparaissent sur le téléphone apparaissent également sur la montre. Pas de développement spécifique dans ce cas.
  • Les applications. utilisées pour envoyer des données. Pour un UI spécifique, ou encore pour les commandes vocales.

Quelques ressources sympa :

UI/UX

Facebook, Twitter sont présents pour nous parler de leur processus de développement de leur application mobile. Un des points qui ressort de ces conférences, c’est que les applications mobiles ne sont clairement plus des “gadgets que l’on propose en plus de l’application Web”.

Il y’a clairement eu des ratés sur le développement d’applications mobiles. Un cas concret : Facebook et sa première application mobile : développée en HTML5 pour simplifiée les développements. Résultat : les développements ont en effet été simplifié, mais expérience utilisateur n’as pas été au rendez vous. Il est nécessaire de revoir la philosophie Web et de se tourner vers une philosophie “Mobile-first”, dans laquelle, on privilégie l’expérience utilisateur. Performance, ergonomie et interface adaptée au device (proche du natif).

Il est important aussi de prendre en compte l’expérience Cross-Plateforme comme favorisant la boucle d’engagement de l’utilisateur : Lui proposer un même application sur plusieurs platformes assure qu’il utilise cette application plus fréquemment. L’idée étant de fidéliser un utilisateur avec un application, peut importe le support. Plus il s’engage et plus la monétisation est potentiellement importante.
(c’est l’approche proposée par Amazon GameCircle pour les jeux : on commencer une partie sur un device et on la continue sur un autre).
Mais avant tout il faut savoir ce que veut l’utilisateur. le comment et pas le pourquoi. L’utilisation et pas application.
L’idée pour connaitre l’engagement, c’est de mesurer, d’apprendre des utilisateurs. Mesurer, Itérer et améliorer.
L’approche A/B Testing et de feature flipping est beaucoup mise en avant pour le développement mobile. Le Feature-flipping a une vraie plus-value, notamment pour corriger rapidement des features non compatibles sur certains devices.
Enfin, il est toujours utile de tester l’application sur soi même : Eat you own dogfood est également une bonne pratique pour tester l’application en condition réelle sur soit même. Pour les bêta tests, il est bon de savoir que Google propose des canaux de distributions pour les applications en cours de développement : Google Play Alpha Channel et Google Play Bêta Channel.

Côté UI, c’est évidemment le Material Design est qui est la grosse évolution graphique apportée par Android L. On retrouve des concepts nouveaux (Floating Action Button) ou qui sont mis beaucoup plus en avant (Cards). Ces deux dernières fonctionnalités sont rétro-compatibles avec le support v7.
Les animations et les Ripple Effets sont par contre spécifiques à Android L (API 21).
L’idée derrière Material Design est de proposer un thème Cross-platform qui devienne le standard graphique pour les applications Google, que ce soit les apps Web (Google Drive récemment), Android ou Chrome OS.

aucun rapport mais rigolo : Gource. Un outils de visualisation graphique d’un dépot SVN.

Quelques ressources intéressantes :

Android Development

Beaucoup de présentation sur des libraries ou langages permettant de simplifier les développements Android. Ce qu’il en ressort, c’est que le développement d’application Android est assez boiler-plate et qu’il faut mettre en place des approches pour simplifier le développement et faciliter la maintenance. Plusieurs approches :

  • Injection de ByteCode à la compilation via Mimic ou AfterBurner ( Stéphane Nicolas).
  • Développement en Groovy sur Android (Guillaume Laforge). Java est trop verbeux. Groovy réduit les coûts de développement, facilite la lecture et la maintenance.
  • utilisation de la Functional Reactive Programming via RxJava et RxAndroid pour fluidifier les compositions d’appels asynchrones.

Zoom sur les bibliothèques d’annotations : ButterKnife / RoboGuice (@InjectViews), Dagger / RoboGuice (@Inject), Otto / RoboGuice / EventBus (@Observes), Memento / IcePick (@Statefull), Hugo (@Log). Il serait judicieux que ces annotations deviennent des standards intégrés à une prochaine version d’Android (…mais quand? )

Zoom sur les bibliothèques pour mieux découper son application : Dagger (dependency injection), Mortar découpage MVC.

Aucun rapport mais utile : Droid@Screen, l’outil que les conférenciers utilisent pour partager leur écran pendant les conférences. Il souffre toujours d’un petit problème de lag, mais reste un outil super utile pour les présentations.

Concernant les processus de développements, les retours laissent apparaitre que les outils manquent parfois de maturité.
C’est notamment le cas sur les frameworks de tests d’intégration (Functional Testing). Deux challengers se démarquent : Robotium et Espresso. Malgré que ce dernier soit encore en version bêta, il semble plus prometteur : meilleurs performances, développement plus cohérents, système de matchers issus de Hamcrest plus adaptable.
Concernant les IDE, tout le monde semble être passé à Android Studio (lui aussi encore en bêta) et utilise Gradle pour la construction des applications.
Beaucoup de bêta et les retours laissent à penser que l’écosystème n’est pas encore tout à fait mature !

Les stands

Microsoft est présent pour nous parler de Xamarin, mais également de remettre en avant Visual Studio qui intègre Unity - la plateforme de développement de jeux 3D - et également Cordova et le debugger JS intégré.

Amazon est présent pour nous présenter son écosystem Amazon App Store, qui porte pour l’instant 240’000 applications mais qui a pour vocation de rattraper le Play Store (qui lui en a quelques 1 400 000 !). L’idée de l’Amazon App Store est de centraliser les achats ( qu’ils soient physiques, virtuels ou in-app) et de faciliter la monétisation des apps en proposant des achats One-Click in-app (Acheter un T-shirt Angry bird dans son jeu sera possible !). Ils présentent également leur gamme Fire, notamment le FirePhone et son interface Dynamic Perspective qui suit les mouvements de tête.

Intel présente les outils de tracking permettant d’optimiser les performances des applications, notamment via GPA orienté GPU ou INDE.
Il y’avait aussi Alcatel qui présentait sa gamme One Touch, notamment un téléphone “compagnon” qui se connecte en bluetooth sur une tablette trop grosse pour tenir dans la poche; ID.Apps, éditeur d’applications mobiles, Xebia, Octo, et également GenyMobile qui présentait notamment GenyMotion, son “super-fast Android Emulator”.

zoom sur : GenyMotion, anciennement AndroVM, propose une VM Android qui s’éxecute dans un VirtualBox. Il ne s’agit pas d’émulation, mais bien de VM (sur archi x86). Bien plus performant. GenyMotion intègre en plus un ensemble d’interface pour simuler les capteurs. Approche très intéressante pour les tests. avec prochainement une possibilité de scripting à la Vagrant.

Pour finir

Pour ceux qui souhaitent voir des photos, vous pouvez jeter un oeil sur Flickr BeMyApp
Tous les talks ont été filmés : je mettrai le lien dès que les vidéos seront dispo.
Vous pouvez quand même jeter un oeil sur le site officiel.

Petit apparté : Après plusieurs mois de non-blogging, je me décide enfin à reprendre l’écriture. La plateforme Blogger est sympa, mais l’éditeur WYSIWYG, me laisse un peu…perplexe, d’autant plus qu’il me crache du code HTML pas forcément tip-top que je dois me retaper à la main.
Du coup, je tente une nouvelle approche, en écrivant en Markdown dans StackEdit qui permet de publier vers Blogger (entre autre).

jeudi 17 avril 2014

Utiliser l'Omnibox pour faciliter les recherches du développeur

Le développeur est souvent amené à effectuer des recherches spécifiques pour lesquelles Google n'est pas le meilleur moteur de recherche : code source, docs d'API, icônes, librairies...

Il est possible de customiser facilement l'Omnibox de Chrome pour nous aider à faire des recherches plus rapides en préfixant rapidement par un mot-clé simple. Le tout de manière simple, paramétrable et facilement adaptable à tout contexte.

Pour l'utiliser, il suffit de préfixer la recherche dans l'Omnibox avec le mot-clé pour effectuer la recherche sur le moteur adapté.

  1. Cliquer sur la barre 'Omnibox"
  2. puis "Edit Search Engines..."
  3. Ajouter un nouveau moteur de recherche, sachant que la plupart des outils proposent des URL de recherches, il suffit de les trouver et paramétrer des "raccourcis" pour ces recherches. Dans cette URL, le %s sera remplacé par le terme de la recherche.

Quelques exemples utiles pour le développeur :
Code source
mot-clé source pour http://grepcode.com/search/?query=%s

manpages (d'Ubuntu)
mot-clé man pour http://manpages.ubuntu.com/cgi-bin/search.py?q=%s

Artefacts Maven
mot-clé artefact pour http://search.maven.org/#search%7Cga%7C1%7C%s

docs Web
mot-clé doc pour Devdocs.io

et plein d'autres :



On pourrait même imaginer des moteurs pour des ressources manquantes : Code ASCII, entité HTML, couleur, code d'erreur SGBD,

Et au sein de l'entreprise ?
Nexus, OpenGrok, ou Jenkins offrent des possibilités pour s'intégrer à l'Omnibox de Chrome de manière très simple.

pour aller plus loin.
Les sites mettant en place des descriptions OpenSearch sont automatiquement ajoutées en tant que moteur dans l'omnibox (c'est le cas notamment de StackOverflow). Pour comprendre le principe d'OpenSearch, je conseille de faire un tour sur la page dédiée et d'aller voir sur le Projet Mycroft qui recense un grand nombre de moteurs de recherches intégrable (ou pas) à l'Omnibox.


jeudi 16 janvier 2014

Arquillian, JPA et Datasets (2/2) : Utiliser Arquillian Persistence Extension

Dans la première partie de l’article sur Arquillian, JPA et Dataset, je montrais comment paramétrer nos test pour gérer les transactions, l’injection de données. Au final, le résultat est assez compliqué à mettre en place. Dans cet article, je vais faire voir comment utiliser une Extension d'Arquillian pour nous faciliter la vie.

Tout d’abord, pour répondre aux problématiques évoquées dans l'article précédent,nous pourrions utiliser DBUnit ou Unitils, mais le problème est que ces frameworks ne sont pas compatibles avec CDI. Nous allons donc utiliser "Arquillian Persistence Extension". La solution est assez jeune (encore en Alpha !) mais elle est prometteuse car elle apporte des réponses et simplifie le travail du développeur. Je vais donc entrer plus en détail sur son utilisation dans cet article.

Arquillian Persistence Extension offre plusieurs fonctionnalités :
  • Assure la gestion des transactions pour chaque test unitaires 
  • Assure l’injection de jeux de données de tests spécifiquement pour chaque test dans différent formats. Cette fonctionnalité est offerte par @UsingDataSet 
  • Compare les données en fin de test avec un jeu de test attendu. offert par @ShouldMatchDataSet 
  • Eviction du cache de second niveau entre le passage des tests. 
Je ne reviens pas ici sur les Entity et Dao expliqué au chapitre précédent, ce sont les mêmes. Je vais me concentrer sur la classe de test.

 

 Un peu de configuration

Malgré que H2 ne soit pas indiqué explicitement comme étant supportée, j’ai fais mes tests sur cette version et confirme qu’elle est fonctionnelle avec l’extension persistence.
Il faudra juste prendre en compte deux particularités : Le Dialecte doit être pris en compte dans le persistence.xml
<properties>
    <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
    <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
    <property name="hibernate.show_sql" value="true"/>
    </properties>

Le DataTypeFactory doit être prise en compte dans le fichier de configuration arquillian.xml pour pouvoir s’interfacer avec H2.
<extension qualifier="persistence-dbunit">
    <property name="datatypeFactory">org.dbunit.ext.h2.H2DataTypeFactory</property>
</extension>
Enfin, on ajoute la dépendance Maven
<dependency>
  <groupId>org.jboss.arquillian.extension</groupId>
  <artifactId>arquillian-persistence-impl</artifactId>
  <version>1.0.0.Alpha6</version>
  <scope>test</scope>
</dependency>
Une fois la dépendance Maven apportée, nous avons accès à plusieurs annotations (dont @UsingDataSet et @ShouldMatchDataSet) qui vont nous simplifier la vie.

 

La classe de test

Dans notre classe de test WeatherDaoImpl, je ne définis pas d'EntityManager, ni de UserTransaction. La prise en compte de la transaction est automatique dès lors que la méthode est annotée avec @UsingDataSet ou @ShouldMatchDataSet. Pour le développeur, pas besoin de créer des transactions manuellement, ni même d’insérer l’EntityManager à ce niveau.
@RunWith(Arquillian.class)
public class WeatherDaoTest {
  
    @EJB
    WeatherInfoDao dao;

    @Test
 ...

 

@UsingDataSet

L’annotation @UsingDataSet permet d’injecter un jeux de données. Le fichier peut être au format XML, JSON, YAML ou même SQL. Le format est automatiquement pris en compte grâce à l’extension du fichier.
@Test
@UsingDataSet("datasets/weather.json")
public void accessTemperature() {
  
    WeatherInfo info = dao.getInfoFromTown("NTE");
    Assert.assertEquals("32°C", info.getTemperature());
}

Exemple de fichier de DataSet
{
"WeatherInfo":
  [
    {
      "townCode" : "NTE",
      "townName" : "Nantes",
      "temperature" : "32°C",
      "isRainingInAnHour" : false
    },
    {
      "townCode" : "BRT",
      "townName" : "Brest",
      "temperature" : "32°C",
      "isRainingInAnHour" : false
    }
  ]
}

Le même DataSet en YAML offre plus de concision.Je pars sur ce format dans l'article, mais il faut savoir que le développeur est libre de choisir le format selon ses besoins au cas par cas (un test en JSON, un autre en XML par exemple). Cependant, je préconiserais quand même d'avoir une uniformisation des formats de fichiers pour simplifier la maintenabilité de l'application.
WeatherInfo:
  - townCode: NTE
    townName: Nantes
    temperature: 32°C
    isRainingInAnHour: false
  - townCode: BRT
    townName: Brest
    temperature: 18°C
    isRainingInAnHour: true

Quelques points à savoir sur l’utilisation des DataSets :
  • la valeur de la PK des enregistrements n’est pas obligatoire dans le DataSet. Elle sera auto-incrémentée automatiquement lors de chaque ajout. mais... 
  • Persistence extension s’appuie sur DBUnit. Lors de l”ajout, la PK est incrémentée mais la valeur de la séquences utilisée par l’AUTO_INCREMENT n’est pas mise à jour. Ceci est un important à savoir car si l’on souahite ajouter un nouvel enregistrement depuis le test, celui-ci risque de se positionner sur un enregistrement existant (le numéro 1 par exemple) et lancer une exception d’unicité (du type : A different object with the same identifier value was already associated with the session). 
  • Lorsque l’on fait des tests de création, il est donc indispensable d’ajouter explicitement les valeurs de PK dans les datasets en les positionnant à des valeurs hautes pour éviter les collisions (c’est à dire supérieur au nombre d’enregistrement qui pourraient être créés pendant les tests). 
  • Lors des tests de lecture ou de modification de données existantes, les valeurs de PK pourront être omises. 
Ainsi, dans ce second test :
@Test
@UsingDataSet("datasets/weather2.yml")
public void createNewInfo() throws Exception {
    WeatherInfo info = new WeatherInfo("LMS", "Le Mans", "28°C", false);
     
    dao.saveInfo(info);
    Assert.assertEquals(3, dao.getAllInfos().size());

}

on effectue l'injection avec le DataSet weather2.yml suivant :
WeatherInfo:
  - id: 998
    townCode: NTE
    townName: Nantes
    temperature: 32°C
    isRainingInAnHour: false
  - id: 999
    townCode: BRT
    townName: Brest
    temperature: 18°C
    isRainingInAnHour: true

 

@ShouldMatchDataSet

L’annotation @ShouldMatchDataSet ajoute une assertion supplémentaire sur l’état de la base de données attendue en fin d’un test. Il indique donc un DataSet résultat attendu. Si l’état de la base est identique au Matching Dataset, alors le test est OK. Si la base est dans un autre état, le test est en échec.
Pour les matching Datasets, il est possible (et d’ailleurs très préférable) d'exclure certaines colonnes. Par exemple, la colonne des valeurs de PK n’as en général aucun intérêt à être testée et peux donc être exclus avec excludeColumns.

@Test
@UsingDataSet("datasets/weather2.yml")
@ShouldMatchDataSet(value="datasets/expected-weather2.yml", excludeColumns="id")
public void createNewInfo() throws Exception {
  
    WeatherInfo info = new WeatherInfo("LMS", "Le Mans", "28°C", false);
    dao.saveInfo(info);
}
WeatherInfo:
  - townCode: NTE
    townName: Nantes
    temperature: 32°C
    isRainingInAnHour: false
  - townCode: BRT
    townName: Brest
    temperature: 18°C
    isRainingInAnHour: true
  - townCode: LMS
    townName: Le Mans
    temperature: 28°C
    isRainingInAnHour: false

A savoir :
  • On peut également utiliser @ShouldMatchDataSet seul, sans avoir au préalable inséré de Dataset avec @UsingDataSet. Ca peut être utile lors des tests de méthode de création. 
  • La méthode de test annotée @ShouldMatchDataSet est executée au sein d’une transaction.
@Test
@ShouldMatchDataSet(value="datasets/expected-weather2.yml", excludeColumns="id")
public void createNewInfoFromScratch() throws Exception {
     
    WeatherInfo info1 = new WeatherInfo("NTE", "Nantes", "32°C", false);
    WeatherInfo info2 = new WeatherInfo("BRT", "Brest", "18°C", true);
    WeatherInfo info3 = new WeatherInfo("LMS", "Le Mans", "28°C", false);
        
    dao.saveInfo(info1);
    dao.saveInfo(info2);
    dao.saveInfo(info3);
}

 

@Transactional

Cette annotation permet de rendre une méthode de test transactionnelle, simplement en annotant la méthode ! Cette annotation est prise en compte par le Arquillian Transaction Extension. (extension dont je n’ai pas parlé auparavant mais qui est tiré de manière transitive par le Arquillian Persistence Extension). Pour rappel, les tests classiques (simplement annoté avec @Test) ne sont pas exécutes dans un contexte transactionnel.
Par exemple, le code ci-dessous finit en erreur car il s’exécute hors transaction. (Pour rappel, le DAO est marqué pour s'exécuter au sein d'une transaction car TransactionAttribute est MANDATORY).

@Test
public void createNewInfoFromScratchWithoutTransaction() throws Exception {
  
    WeatherInfo info1 = new WeatherInfo("NTE", "Nantes", "32°C", false);
    WeatherInfo info2 = new WeatherInfo("BRT", "Brest", "18°C", true);
         
    dao.saveInfo(info1);
    dao.saveInfo(info2);
   
    Assert.assertEquals(2,  dao.getAllInfos().size());
}

Dans ce cas, le TU doit donc obligatoirement être transactionnel. Pour résoudre cette erreur, il suffit simplement d’annoter la méthode @Transactional.

import org.jboss.arquillian.transaction.api.annotation.Transactional;
... 
@Test
@Transactional
public void createNewInfoFromScratchWithTransaction() throws Exception {
    WeatherInfo info1 = new WeatherInfo("NTE", "Nantes", "32°C", false);
    WeatherInfo info2 = new WeatherInfo("BRT", "Brest", "18°C", true);
        
    dao.saveInfo(info1);
    dao.saveInfo(info2);
     
    Assert.assertEquals(2,  dao.getAllInfos().size());
}

 

En conclusion

L’extension nous permet de simplifier énormément l’écriture de tests JPA dans un contexte JavaEE par rapport à l’article précédent. Le code reste clair, facile à comprendre et la dissociation entre code et données de tests est un gros avantages pour la maintenabilité des tests.

De plus, L’extension offre des possibilités complémentaires : injection de scripts SQL, création de schéma (si utilisation hors ORM par exemple), insertion SQL en @Before/@After, définition de stratégies de Cleanup, éviction de cache de second niveau.

En revanche, ce qui pêche un peu, c’est le manque de documentation. J’ai du dépouiller le code source pour comprendre comment utiliser l’appli. Cependant, même en Alpha mais est déjà fonctionnelle et pourra vous simplifier la vie lors de la création des Tests de vos application JavaEE.

A tester donc !

Pour aller plus loin

Les sources des l'article sont disponibles sur Github.
 Les sources du projet “Arquillian Persistence Extension” sont disponibles sur Github.
Comprendre les problématiques d’unicité de valeurs de PK dans DBUnit : http://sipxconfig.blogspot.fr/2005/03/dbunit-seed-data-use-high-primary-ids.html

mercredi 8 janvier 2014

Arquillian, JPA et Datasets (1/2) : Première prise en main

Le premier article Tester son application JavaEE avec Arquillian montrait comment effectuer des tests sur des composants EJB.

Lors de l’écriture des tests unitaires, il est nécessaire de tester les composants touchant la couche persistance JPA dans un contexte transactionnel. Nous allons donc voir comment prendre en compte la persistance et les transactions lors des tests avec Arquillian.

Quelques points à savoir avant de commencer :
  • Lors du passage des tests unitaires, il est important d’isoler les données utilisés pour les tests de ceux utilisées pour l’intégration. Il est donc nécessaire de créer une base dédiée pour les tests. Créer une base "manuellement" pour chaque test est trop long et pas industriel. Il est donc nécessaire de prendre une base embarquée type HSQLDB ou H2. Ici, nous prenons une base de donnée embarquée H2 car elle est déjà intégrée dans le profile par défaut JBoss, il n’y a donc pas de configuration supplémentaire à faire dans les drivers JDBC. 
  • il est préférable de garder les TU au sein d’une transaction. L’état de la base reste propre entre chaque passage de test et les données modifiées pendant les tests doivent être rollbackées.
  • C’est lors des tests de persistance qu’Arquillian prend tout son sens par rapport à Spring Test. En effet, lors de l’écriture de l’article précédent, comme on ne travaillait qu’avec des EJB, on aurait pu utiliser Spring Test, puisqu’il interprete certaines annotations standards (notamment @Inject, @EJB). A partir du moment ou l’on utilise JPA, Datasources, Transactions, Producers et Resources alors Spring Test n’est plus en capacité de répondre à nos besoins : il nous faut un conteneur EE.
  • En corollaire du point précédent, les tests en mode "JBoss Embedded" ne sont plus possible avec JPA et les transactions. Il faudra utiliser un déploiement en Container : managed ou remote.
  • Le site d’Arquillian propose un article complet disponible à cette adresse : http://arquillian.org/guides/testing_java_persistence/ Cet article est une bonne base et nous allons voir comment aller plus loin en injectant des données et gérer les transactions.

 

Création de l’Entité JPA

L’entité JPA assure le mapping avec la base de données. Dans le cas de test, l’Entity WeatherInfo porte des infos de temps et est mappée avec une table. Les détails de l’implémentation de la table ne nous importe pas ici, on va laisser l'ORM générer la table.
L’entité doit être annotée @Entity et contenir une clé primaire représentée par @Id.
@Entity
public class WeatherInfo implements Serializable {
     
    @Id @GeneratedValue
    private long id;
    private String townName;
    private String townCode;
    private String temperature;
    private boolean isRainingInAnHour;
    ...

 

Création du Dao

Le Dao (Object d’Accès au Donnés) sur l’entité WeatherInfo est représenté sous la forme d’un EJB Stateless avec l’annotation @Stateless.
L’EntityManager est injecté dans l’EJB avec @PersistenceContext. Pour simplifier l’exemple, les méthodes du Dao effectuent des créations de requêtes depuis le PersistenceContext. J’ai annoté le Dao en tant que @TransactionAttribute = MANDATORY : Je force le Dao a être marqué pour s'exécuter au sein d'une transaction. Ce flag est une sécurité et assure qu'il est bien appelé au sein d’une transaction déjà ouverte. Pour le respect de l’architecture en couche, c’est primordial. Si le Dao est appelé depuis un service, alors la transaction est portée par le service et est utilisée par le Dao. Si un autre appelant (service IHM par exemple) appelle directement le Dao hors transaction, c’est qu’il ne respecte pas les couches de l’architecture et n’est pas "autorisé" à être appelé (ce qui finira en Exception).

@Stateless
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class WeatherInfoDao {

    @PersistenceContext
    EntityManager em;

    public WeatherInfo getInfoFromTown(String townId) {
        WeatherInfo info = em.createQuery("select w from WeatherInfo w where w.townCode='"
                          + townId + "'", WeatherInfo.class).getSingleResult();
        return info;
 }

 

Configuration JPA

Le fichier persistence.xml est disponible dans src/test/resources/test-resource.xml. Il est injecté dans l’archive grace à Shrinkwrap et renommé en persistence.xml pour que le mapping JPA soit pris en compte. (je ne m’étends pas sur les spécificités JPA ici). Ce fichier définit le jta-data-source à utiliser ainsi que les propriétés spécifiques de l'ORM, et pour lequel on demande la création du schéma (create-drop). le dialect doit être org.hibernate.dialect.H2Dialect pour H2.

<persistence version="2.0" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns="http://java.sun.com/xml/ns/persistence" xsi:schemalocation="
        http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="test">
        <jta-data-source>jdbc/arquillian</jta-data-source>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
            <property name="hibernate.show_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

 

Déploiement du datasource

Même si je fais les tests dans une base de données embarquées, je dois définir un Datasource H2 et le déployer au sein de JBoss.
Pour les tests, je peux très bien intégrer ce datasource dans le WEB-INF du WAR pour qu’il soit déployé en même temps que l’application. (les fichiers nommés en *-ds.xml sont reconnus et déployés en tant que datasource, c’est pour cette raison que le fichier weather-ds.xml en pris en compte en tant que datasource lors du déploiement de l’application).
Cette façon de faire est donc idéale pour les micro-déploiements des tests unitaires.
Ce datasource est défini comme suit :
<datasources xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns="http://www.jboss.org/ironjacamar/schema" 
        xsi:schemalocation="http://www.jboss.org/ironjacamar/schema
        http://docs.jboss.org/ironjacamar/schema/datasources_1_0.xsd">
    <datasource enabled="true" jndi-name="jdbc/arquillian" pool-name="ArquillianEmbeddedH2Pool">
        <connection-url>jdbc:h2:mem:arquillian;DB_CLOSE_DELAY=-1</connection-url>
        <driver>h2</driver>
    </datasource>
</datasources>
Ce qui est important à voir ici, c’est le JNDI name qui est le même que celui référencé dans le persistence.xml.

 

Ecriture de notre classe de tests

Je reprends le même cas de test de l’article précédent, que je vais étoffer pour prendre en compte les tests de Dao. Dans la classe de test WeatherDaoTest, j'injecte notre Dao, puis l’EntityManager ainsi que le UserTransaction.
@RunWith(Arquillian.class)
public class WeatherDaoTest {
    @EJB
    WeatherInfoDao dao;

    @PersistenceContext
    EntityManager em;
     
    @Inject
    UserTransaction utx;

On remarque qu’il faut explicitement enlister l’EntityManager dans la transaction JTA. Cette étape est nécessaire car j'utilise les deux ressources indépendamment. Cela peut sembler anormal si on utilise JPA depuis un EJB car dans ce cas, l’enlistement est automatique, mais ici, il faut l’ajouter explicitement. Arquillian exécute les méthodes @Before et @After dans le conteneur, respectivement avant et après les méthode de tests.
 La méthode @Before est invoquée après que les injections (EJB, em et utx) aient eu lieu.
@Before
    public void preparePersistenceTest() throws Exception {
        clearData();
 insertData();
 startTransaction();
    }
J'ai besoin d’assurer une injection et suppression de données et pour chaque cas de test. Ces injections/suppressions de données sont prises en compte dans les méthodes clearData() et insertData() et appelées depuis la preparePersistenceTest().
preparePersistenceTest() est appelé dans le @Before, la transaction n’est donc pas encore ouverte. Il faut que ces méthodes aient la responsabilité d’ouverture/fermeture des transactions pour assurer l’ajout de données. L’ajout de données est réalisé en créant les entités et en les persistant :

private void insertData() throws Exception {
    utx.begin();
    em.joinTransaction();
     
    // on ajoute des objets
    WeatherInfo info1 = new WeatherInfo("NTE", "Nantes", "32°C", false);
    WeatherInfo info2 = new WeatherInfo("BRT", "Brest", "18°C", true);
     
    em.persist(info1);
    em.persist(info2);
     
    utx.commit();
    // clear the persistence context (first-level cache)
    em.clear();
}

private void clearData() throws Exception {
    utx.begin();
    em.joinTransaction();
    em.createQuery("delete from WeatherInfo").executeUpdate();
    utx.commit();
}
Une fois les ajouts de donnés effectués, la méthode preparePersistenceTest() va lancer la transaction en appelant le startTransaction() qui va ouvrir la transaction pour les tests.

private void startTransaction() throws Exception {
    utx.begin();
    em.joinTransaction();
}

Une fois le test effectué, la méthode annotée @After est appelée et dans notre cas commiter la transaction.

@After
public void commitTransaction() throws Exception {
    utx.commit();
}

Les sources complètes de l’application de tests sont disponibles ici. Je vous invite à y jeter un œil pour mieux comprendre ce qu’elles font. Les tests sont ici présentés en utilisant Wildfly !!!! Attention, pour Wildfly, il faut modifier la dépendance, et pour l’instant, Wildfly n’est pas en Release et donc le container plugin n’est pas pas encore disponible en version Release non plus. La seule différence pour nous est de prendre en compte le container dans la version adéquate, c’est à dire :

<dependency>
  <groupId>org.wildfly</groupId>
  <artifactId>wildfly-arquillian-container-managed</artifactId>
  <version>8.0.0.Beta1</version>
  <scope>test</scope>
</dependency>

 

En conclusion

C'est une approche intéressante et fonctionnelle, mais qui reste compliquée :
  • Beaucoup de code technique et qui nécessite une maitrise du cycle de vie des test unitaires et des transactions (@Before, @After...).
  • Des méthodes de chargement de données communes à toutes les méthodes de tests. Peut-être que dans certains cas de tests précis, on souhaiterait ne charger que certains lots de données : avec cette approche, on ne peut pas.
  • Une création de données depuis du code Java, explicite et donc très (trop!) verbeux lorsque l’on dispose de beaucoup de jeux de données. De plus, le code Java n'est pas non plus le format le plus adapté pour représenter des jeux de données (difficulté de lecture).
  • Pas de tests sur le jeu de données attendu. Comment assurer que les données de sorties sont bien celles que nous attendions pour notre cas de test précis ?
Bien sûr, toutes ces problématiques pourraient être résolues, mais demandent beaucoup de code technique pour être mis en place. Dans un prochain article, nous allons voir comment tirer parti de l’extension Arquillian Persistence Extension pour faciliter les tests Arquillian avec JPA et datasets.

Pour aller plus loin :