07 mai 2014

test des accès base

Je vous présentais hier un petit outil bien fait pour écrire les tests d'API REST. Sur une application qui fait autre chose qu'un hello world, il faut cependant "bouchonner" les resources externes pour rendre ces appels prédictibles et donc reproductibles dans le cadre de tests automatisés.

Une pratique courante de test consiste à utiliser des Mocks, en considérant qu'on veut valider l'utilisation d'un système tiers, pas son implémentation. Dans le cas un peu particulier d'une base de donnée cependant on est confronté à un problème : le SQL

Les accès à la base de donnée sont en effet liés à son interprétation de ces fameuses requêtes, et le seul moyen de s'assurer que leur syntaxe est correcte, correspond au schéma de la base de données, et retourne bien les données attendues, c'est ... de l'exécuter.

Que l'on fasse du JDBC à l'ancienne ou qu'on utilise un outil de requêtage type-name-safe comme jooq le problème reste le même, une requête n'est vraiment fiable que si on a pu la valider sur une base de données réelle. La solution que tout le monde (?) utilise est une base de donnée H2 - base qui tourne en mémoire - ce qui permet de démarrer rapidement et sans complication une base juste pour la durée des tests. Je me suis fait une Rule junit pour monter cette base H2 et appliquer les scripts de création du schéma, qui sont - évidemment - stocké dans mon repo Git avec le code de mon appli (what else ?).

Pour mettre la base dans un état donné et valider le résultat de mes requêtes, à la recherche d'une solution sympa, légère et qui soit un peu plus productive que des fichiers xml DBunit, je suis tombé sur dbassert.

    @Rule
    public InMemoryDataBase dataBase = new InMemoryDataBase();

    @Test
    public void list_items() throws Exception {

        DbAssert db = DbAssert.init("dbassert.yml");
        DbSource h2 = db.source("h2", getClass());
        h2.clean_table("items");
        h2.fixture("items");

        // Proposal returned
        when()
            .get("/items")
        .then()
            .assertThat().body(containsString("Test item"));

    }

Ca reste un chouille verbeux, donc pour faire clair :

le init consiste à configurer DBAssert pour accéder à la base de test. La DBSource sert à définir où trouver les fichier de données - dans le même package que la classe indiquée. On applique ensuite des fixtures, des fichiers yml de données pour la table du nom correspondant. On peut de même comparer l'état de la base après un update.

Quelques points négatifs qui me font penser que c'est un projet encore jeune, ou bien qu'il y a mieux ailleurs :)


  • le projet est sous SVN sur google code. Clairement pas adapté pour la contribution a l'aire de GithHub.
  • la librairie n'est pas disponible sur maven central. J'ai fait un export des sources dans mon src/test/java, vu qu'il n'y a que quelques classes
  • l'API est perfectible. Je voudrais pouvoir initialiser DbAssert via une DataSource - que j'ai préparée par ailleurs dans ma InMemoryDatabase  - plutôt que via un fichier yml
  • l'application d'une fixture utilise un INSERT bête et méchant, sans rechercher les données déjà insérée. On doit donc faire un clean_table systématiquement.
Rien de bien grave, qui devrait pouvoir se corriger via une petite contribution (d'où le premier point).

Si vous connaissez un autre outil comparable, je suis preneur de vos retours d'expérience.




05 mai 2014

test de services Rest

Il y a quelques années, lorsque je développais des applications web (avant de devenir essentiellement un développeur  middleware/backend) il y avait une explosion de frameworks MVC. Struts, WebWork, Wicket, Stripes, pour n'en citer que quelques-uns.

Les applications actuelles tendent vers un modèle à base de vue riches JavaScript et un backend REST, et on voit émerger une foultitude de Frameworks JavaScript et de backend REST.

Je n'ai jamais été très fan des tests web, selenium ou htmlunit, qui ont toujours été délicat à stabiliser et bons pour la casse à la moindre refonte de l'UI. L'évolution vers une architecture JS+REST a l'avantage de permettre au moins de tester facilement la partie REST - je n'ai pas encore exploré les solutions pour la partie Web à proprement parler, c'est quelque part dans ma todo-list ...

Comme toujours, il y a pléthore de frameworks pour un même besoin, chacun avec ses propres spécificités et avantages. Je n'ai pas l'ambition de vous dire avoir trouvé la perle rare ni proposer un palmares exhaustif, vu que je n'en ai essayé qu'un : Rest-assured.

Ce que j'aime bien dans Rest-assured c'est la syntaxe très, très simple et lisible des conditions de test et des assertions, très inspirée de l'approche BDD ("given/when/then") :

    @Test
    public void should_create_user() {
        given()
           .body("{ \"name\": \"nicolas\" }")
        .when()
           .post("/users")
        .then()
           .assertThat().statusCode(CREATED);
    }

Très peu de code parasite dans la description d'un tel test d'API. Que le scénario de test soit lisible même par ma grand-mère (ou pire : ma MOA) comme le voudraient les partisans des tests fonctionnels m'importe peu, par contre que je puisse rapidement définir une condition de test sans partir dans des imbrications d'API sans fin, ça me parle.

L'approche "fluent" (qu'on appelait Builder Pattern dans le temps, mais c'est moins hype) apporte aussi beaucoup en lisibilité. En gros, si on fait abstraction de la ponctuation on peut lire une phrase quasi naturelle (enfin, humanoïde quoi), c'est ce qu'on appelle un Domain Specific Language. On retrouve ce pattern dans tous les frameworks récents, et les lambdas de Java 8 aident pas mal à étendre cette approche à de nouveaux cas d'application.

Ce que je constate surtout, c'est qu'à côté l'explosion des frameworks applicatifs on a aussi une explosion de l'outillage de test, souvent (toujours ?) la cinquième roue d'un développement, mal ficelé, mal entretenu, mal outillé. Pour ce qui me concerne je les redécouvre tardivement. Ne faites pas la même erreur que moi, gardez un oeil sur vos tests et la boite à outil qui va avec.


Bon ceci dit, c'est bien connu "tester c'est douter". Perso j'ai appris à mes dépends à douter et à prendre quelques précautions pour éviter les mauvaises surprises.



Small is beautiful

Faire des tests rapides, un build qui ne dépasse pas quelques minutes, ça reste un voeu pieux confronté à une appli de centaines de milliers (millions?) de lignes avec une architecture alambiquée. Je ne jette la pierre à personne, j'ai moi même largement contribué à rendre ces applications alambiquées à grand coup de Spring-truc et d'AOP.



Aujourd'hui, avec du recul et surtout en travaillant dans une équipe totalement distribuée (donc où il faut savoir limiter les besoins de synchronisation et  de communication), j'apprend les mérite d'une autre approche.

Vive les micro-services.

Le concept de micro-service est expliqué en détail par Martin Fowler et James Lewis. 

Une application mastodonte-monolithique est délicate à faire vivre et ardue à appréhender. Le découpage en couches à la JavaEE et les modules maven associés ne changent pas grand chose à la donne, et au contraire ont tendance à figer de possibles mauvais choix. Si par exemple j'ai développé mon UserRepository avec plein de beaux tests, et que je veux faire un savant refactoring de mon modèle, je vais m'assoir sur tous mes tests, car ils sont ciblés sur la vision de mon architecture à un instant t, alors que le besoin de départ est fonctionnel. On touche ici au distingo test unitaire / test fonctionnel. 

Selon une approche micro-service, plusieurs applications spécialisées prennent en charge des domaines métier restreints. Une équipe plus spécialisée et plus restreinte s'occupe de ce "fragment" et peut plus facilement le faire évoluer. Dans le cas d'une équipe distribuée à la CloudBees, ça donne des équipes de 1 :-)  Chaque service étant très spécialisé et ciblé il est facile de rentrer dans le code "en renfort".

Ces micro-services communiquent ensemble via des APIs, et peuvent utiliser des protocoles binaires pour limiter l'impact d'un appel HTTP sur les performances, mais ce qui compte ce sont les frontières qu'on a défini dans le modèle métier.


Ces frontières et la réduction du scope allègent grandement chaque service et permettent de mettre en place des pratiques de développement efficaces, et d'augmenter l'agilité de chaque service. L'agilité de l'application dans son ensemble s'améliore aussi, malgré un surcout de synchronisation des développements de chaque service. C'est un peu le principe des scrums-de-scrum.

Autre bénéfice qui émerge rapidement, chaque micro-service peut avoir sa propre architecture, voir son propre langage d'implémentation, et donc mieux répondre au besoin "local". 

Ce n'est pas forcement lié, mais si on ajoute Docker dans l'équation, tous ces services peuvent tourner sur la même machine et la communication entre micro-services a alors un coût minimal. Docker permet de donner des quotas à chaque service, et donc d'identifier facilement lequel part en vrille lorsque c'est le cas - ce qui est souvent un challenge ardu sur une grosse appli monolithique. Il permet aussi de gérer un dimensionnement différent de chaque service, un cycle de vie différent, etc.

Bref, vous l'aurez compris, je suis fan :)