insideIT.fr : le blog des architectes IT de SFEIR

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

dimanche 20 juin 2010

Tests java asynchrone : AssertRetry

Je souhaite ici partager une expérience lors de l'écriture de tests unitaires en Java

Le Besoin

Ecrire un test dont l'action est asynchrone (ex: batch, thread, workflow, ...)

 

L'Erreur

Ajouter un Thread.sleep() entre l'action et les assertions

Cela va temporairement résoudre votre problème : le test va passer.

Mais cela a de nombreuses contraintes :

  • Votre série de tests va commencer à être très longue.
  • À chaque fois que le test va échouer sur la machine d'un développeur, celui-ci va être tenté d'augmenter le délai d'attente.

Ainsi, la durée de votre test est contrainte par la machine la plus lente.

Personne ne veut avoir une suite de test longue, encore moins une suite qui ne fait qu’attendre.

Le timeout sur les tests apporté par les annotations Junit 4 permet de définir un temps d’exécution maximal pour l’ ensemble de la méthode de test, mais il ne permet pas de placer les assertions à la fin du timeout. Il nous faut donc trouver d’autres solutions.

Solution 0 : Contourner

Si cela est possible, placer les tests plus près du code, et faire un test simple.

Solution 1 : Listener

Réussir à observer la fin du traitement par une attente passive (Pattern observateur), associé à un timeout.

C'est ce qui est fait par exemple dans GwtTestCase

http://code.google.com/intl/fr/webtoolkit/doc/1.6/DevGuideTesting.html#DevGuideAsynchronousTesting

La durée d’exécution du test est ainsi parfaitement ajustée à la durée de l'action testée.

 

public void testDoWithCallback() throws Exception {

      final Async async = new Async();

      async.doWithCallBack(new Async.Callback() {

            public void onSuccess() {

                  finishTest();

            }

                  public void onError(Throwable t) {

                  finishTest(t);

            }

      });

      delayTestFinish(11000);

      assertEquals(10, async.result);

}

Il existe une variante avec wait et notify, mais le code est moins lisible (voir sources). 

Solution 2 : AssertRetry

Dans le cas où vous ne pouvez pas être notifié de la fin du traitement, ce qui était mon cas.

La solution que j'ai trouvée est de faire plusieurs essais successifs afin de vérifier la fin correcte du traitement.

Les assertions sont définies avec un nombre d'essais supplémentaires et une durée d'attente entre les essais.

Ainsi, le test passera avec peu d’essais sur une machine puissante, et passera également sur une machine moins puissante,

jusqu’à atteindre le timeout implicite ( nombre d’essais supplémentaires * durée d’attente), dans ce cas, la dernière assertion échoue.

public void testDoWithoutCallback() throws Exception {

    final Async async = new Async();

    async.doWithoutCallBack();

    new AssertRetry(10,1000, new AssertRetry.AssertTry() {

            public void assertTry() throws Exception {

            assertEquals(10, async.result);

      }

    });

}

 

Conclusion

J’ai trouvé cette solution du AssertRetry efficace et élégante.

Je n’ai pas trouvé d’équivalent dans les frameworks de test, mais si vous en connaissez un, faites-moi signe.

Le code fourni peut être consommé sur place ou à emporter.

 

package fr.wokier.assertion;

 

import org.apache.log4j.Logger;

 

/**

 * Allows to give multiple tries to some assertions, without adding arbitrary and

 * time consuming Thread.sleep() calls

 *

 * @author f_wauquier

 */

public class AssertRetry {

 

    private static final Logger LOGGER = Logger.getLogger(AssertRetry.class);

 

    /**

     * Internal class for AssertRetry

     *

     */

    public interface AssertTry {

        /**

         * Does one or more assertions

         *

         * @throws Exception

         *             eorror or assertion failure

         */

        public void assertTry() throws Exception;

    }

 

    /**

     * Builds an AssertRetry

     *

     * @param retryTimes

     *            number of times the assertTry will be called AGAIN (0 calls it

     *            only one time)

     * @param assertTry

     *            Implements your own AssertTry containing assertions

     * @throws Exception

     */

    public AssertRetry(int retryTimes, AssertTry assertTry) throws Exception {

        retry(retryTimes, 0, assertTry);

    }

 

    /**

     * Builds an AssertRetry

     *

     * @param retryTimes

     *            number of times the assertTry will be called AGAIN (0 calls it

     *            only one time)

     * @param sleepTimeInMillis

     *            sleep time between assertTry calls, in milliseconds

     * @param assertTry

     *            Implements your own AssertTry containing assertions

     * @throws Exception

     */

    public AssertRetry(int retryTimes, int sleepTimeInMillis, AssertTry assertTry) throws Exception {

        retry(retryTimes, sleepTimeInMillis, assertTry);

    }

 

    private void retry(int retryTimes, int sleepTimeInMillis, AssertTry assertTry) throws Exception {

        if (retryTimes == 0) {

            assertTry.assertTry();

        } else {

            try {

                assertTry.assertTry();

            } catch (Throwable e) {

                LOGGER.warn("Try failed (remain " + retryTimes + ") :" + e.getMessage());

                Thread.sleep(sleepTimeInMillis);

                retry(retryTimes - 1, sleepTimeInMillis, assertTry);

            }

        }

    }

}

 

 

Sources

lundi 23 novembre 2009

Devoxx'09; un billet de plus!

Au lieu d'énumérer les présentations auxquelles j'ai assisté, je vais plutôt essayer d'en faire un petite synthèse. Pour commencer, j'aurais beaucoup aimé qu'il y ait eu quelques sujets en relation avec le mouvement 'NoSql' ou le 'Domain Driven Design', malheureusement ça pas été le cas, mais étant simultanée avec la QCon à San Fransisco, il suffirait alors de jeter un petit coup d'œil sur les speakers et le programme pour avoir la réponse (un autre coup d'œil sur les prix aussi :p).

En fait, le programme proposé traduit un peu l'année 2009 pour Java, mise a part EE6, trop peu de nouveautés et encore moins d'innovations; enfin, la encore si on considére que EE6 apporte de réelles nouveautés ou une quelconque innovation; j'aurais plutôt tendance a dire que c'est un re-mixage de quelques de-facto qui ont éclipsés la version précédente. Exception faite ce qui se passe du coté des 'NoSqlistes' avec des projets comme Hadoop et compagnie, qui n'étaient présent que par un petit BOF! Ce qui est a mon propre avis dommage car c'est un thème en pleine ébullition. Du coup, des sujets comme JDK7 ou Spring3 font encore et toujours l'actualité faute de relève mais aussi a cause du retard qu'ils ont eu par rapport a leur roadmap initiale. Seule l'annonce de adoption des 'closures' pour Java7 a créer un peu le buzz autour de ces sujets, quoi que personnellement j'en avais marre de cette histoire et la suivais plus depuis quelque temps. Autre chose qui a laissé sa trace sur cette édition, c'est la crise! Etant à ma première je pouvais pas vraiment m'en apercevoir, mais a en croire les habitués les traces sont assez flagrantes.

En ce qui concerne les présentations auxquelles j'ai assistés, j'en ai retenu spécialement celle de Cameron Puddy, intitulée 'Traditional Programming Models: Stone knives and Bearskins in the Google Age' dont je donne un aperçu a travers une citation tirée de ses slides :

« By 2021, there will be chips with 1024 cores on them. Is parallelism the tool that will make all these cores useful? John Hennessey has called it the biggest challenge Computer Science has every faced. He has credentials that might make you believe him. Allen says that it’s also the best opportunity that Computer Science has to improve user productivity, application performance and system integrity. - Phillip J. Windley ».

Je vous invite, d'ailleurs,a voir cette présentation sur Parleys soit dans les prochains jours en vous inscrivant à la chaine dédiée au Devoxx'09 ou gratuitement l'année prochaine.

Je citerais aussi le 'show' de Robert Martin dit oncle Bob pour la 2éme Keynote et vous invite de jeter un coup d'oeil sur le manifeste des 'artisants développeurs'. Et enfin, la présentation de iBeans, quoique la c'est plus le framework qui a retenu mon attention que la prestation de Ross Mason; En fait, je remarque que toutes les sessions durant lesquelles un framework était présenté par son 'créateur' étaient étrangement trop abstraites ou orientées tutoriel/document de référence, et il y avait un manque de mise en valeur des problématiques auxquelles répondait ou tentait de résoudre le produit en question.

En ce qui concerne le reste, je dirais pas qu'elles sont pas intéressantes mais plutôt que j'en espérais plus. Par exemple je suis resté sur ma faim sur un sujet qui m'intéresse beaucoup et qui était pourtant bien présent a travers trois ou quatre sessions et qui est la performance. En fait j'avais espérer retenir bien plus des Kirk Pepperdine, Dan Hardiker, Simon Ritter et Holly Cummins. Cela dit, comme mentionné par ces speakers ce problème est très difficile a présenté car trop liés aux cas concrets; la preuve, le peu d'ouvrages qui existent dessus.

Enfin, et pour conclure, je dirais que j'ai été globalement satisfait par cette participation et surtout par quelques discussions intéressantes avec d'autres participants, sans oublier les frites!

lundi 9 novembre 2009

Les rencontres spring 2009, Spring 3 par Arjen Poutsma

Signes distinctifs

Java 5

La première information qui saute aux yeux c'est que Spring 3 est complétement écrit en Java 5, ce qui d'emblée signifie qu'un projet utilisant Spring 3 est nécessairement compatible Java 5. ça peut paraitre brutal mais il faut savoir que depuis le 30 Octobre dernier, Java 5 n'est plus supporté par sun lire ceci.
pour illustrer ce changement voici un exemple qui met l'accent sur l'utilisation massive de Java 5 :

T getBean(String name, Class<T> requiredType)
la BeanFactory, et qui représente de loin une brique assez importante dans Spring, est elle même -Généric complient-

plus besoin d'XML, ou presque

l'autre nouveauté apportée par Spring 3, c'est la possibilité de configurer la bean factory sans le moindre XML ! cependant, Spring 3 reste totalement rétro compatible avec les version précédentes.

Utilisation de l'annotation @Configuration :
Configuration
public class ApplicationConfig {
}

la classe ApplicationConfig sera donc responsable de l'instanciation des Beans, pour déclarer un Bean, rien de plus simple :
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}

Cette approche permet d'une part de donner au développeur le moyen de contrôler du code spécifique avant de rendre le Bean, d'autre part ça permet au code de l'application d rester totalement indépendant de la plateforme. L'autre façon est par l'utilisation des Meta Annotation

Restructuration du repository du code

l'organisation des modules de Spring 3 a été complétement revue, désormais le code source de chaque module est maintenu séparément.
Plus d'information sur le site de Spring. <<LINK>>

Spring Expression Language

Spring EL et qui s'apparente à Unified EL, propose des possibilités de simplification très puissantes et notamment lors de la définition des Beans dans une XML ou par annotations.
voici un exemple illustrant les possibilités qu'offre Spring EL :
<bean class="mycompany.RewardsTestDatabase">
    <property name="databaseName" value="#{systemProperties.databaseName}"/>
    <property name="keyGenerator" value="#{strategyBean.databaseKeyGenerator}"/>
</bean>

dans cet exemple, l'attribut databaseName et keyGenerator font référence à deux Beans précédemment initialisés dans le contexte.
le même comportement est facilement reproductible par l'utilisation des annotations selon l'exemple suivant :

@Repository
public class RewardsTestDatabase {
@Value("#{systemProperties.databaseName}")
public void setDatabaseName(String dbName) {
}
@Value("#{strategyBean.databaseKeyGenerator}")
public voidsetKeyGenerator(KeyGenerator kg) {
}
}
La résolution des @Value se fait automatiquement et dynamiquement par Spring au Runtime, ce qui offre une nouvelle possibilité pour les développeurs qui la prise en compte à chaud par Spring 3 d'un changement effectué dans les fichiers de configurations .properties.

Web

Spring MVC

Pas de nouveauté concernant le volé Spring MVC depuis la version 2.5 de Spring, qui a introduit l'utilisation des Annotations pour construire des Contrôleurs MVC dans une applicaiton, ce que Spring 3 ajoute -ou supprime- c'est la déprécation de "SimpleFormController" dans le but de faire orienté les nouveaux développements vers l'utilisation "systématique" des annotations pour les contrôleurs Spring.

Amélioration du support REST

Le support de REST a été amélioré dans cette version de Spring 3. REST est devenu en quelques années le standard de facto pour quiconque souhaite mettre en place une architecture orientée service par l'utilisation de Web Service, cependant REST est beacoup plus que ça. Le modèle de développement REST est assez simple et passe par l'utilisation intensive des Annotations. Un aspect non négligeable que REST permet d'atteindre est la normalisation des URI, je m'explique, pour faire passer des valeurs à travers une URL standard en GET le moyen le plus simple est de faire comme ci-dessous :
http://www.application.com?VARIABLE=VALUE,....
le problème qui se pose est qu'un certain nombre de proxy refuserons ce genre d'URL. le format des URI REST sont de pure URL qui respecte le standard HTTP.
pour pouvoir utiliser REST avec Spring 3, rien de plus simple :

URI Templates
@RequestMapping("/hotels/{hotelId}")
public String getHotel(@PathVariable String hotelId, Model model)
List hotels = hotelService.getHotels();
model.addAttribute("hotels", hotels);
return "hotels";
}
Quand une requête arrive à "/hotels/1", le serveur repondera par le renvoi de la chaine "hotels", noté cependant que {hotelId} dans "@RequestMapping" au début de la classe est repris dans la déclaration de la méthode "getHotel" par l'utilisation de l'annotation "@PathVariable".

D'autres possibilités sont offertes et notamment avoir plusieurs variables sur le même path ce qui permet de faire des filtres assez affinés :

@RequestMapping(value="/hotels/{hotel}/bookings/{booking}", method=RequestMethod.GET)
public String getBooking(@PathVariable("hotel") long hotelId, @PathVariable("booking") long bookingId, Model model) {
Hotel hotel = hotelService.getHotel(hotelId);
Booking booking = hotel.getBooking(bookingId);
model.addAttribute("booking", booking);
return "booking";
}
Je vous conseil vivement d'aller sur le blog de Arjen Poutsma qui vous donnera de plus amples informations.

View

Spring-MVC permet au @Controller de décider quelle vue rendre pour une requête donnée, via ViewResolver. Dans un scénario RESTful, c'est le client qui décide du rôle de la représentation acceptable (XML, HTML, JSON), en passant par l'en-tête HTTP-Accept. Le serveur répond avec la représentation livrés via l'entête Content-Type. Ce processus est connu comme la négociation de contenu.

Spring Integration

Spring Integration est un framework léger d'intégration à l'image de Apache Camel ou Mule, il permet par exemple l'échange de données entre applications Spring se trouvant dans plusieurs instances JVM à travers un canal de communication. Pas de bouleversement concernant Spring Integration pour la version 3 de Spring à part le fait d'une meilleure prise charge ,un modèle simplifié d'utilisation et un découplage Framework-application amélioré.

Support de JEE 6

Une prise en compte du support de la très prochaine version JEE 6 annoncée pour la mi Décembre 2009 existe déjà dans Spring 3 et notamment le support de la JSR 330. La version Spring 3.1 aura pour mission la prise en compte totale de JEE 6.

Depricated & Pruned

Plusieurs packages et Classes font désormais partie du grand nettoyage du printemps engagé par les équipes de Spring et cela dans le but d'avoir du code encore plus facilement "maintenable", évolutif et moins complexe. Je vous invite vivement de jeté un coup d'œil dans la documentation de Spring pour prendre en compte les changements dans vos projets respectifs.

Téléchargements

Voici comment vous devez faire si vous voulez utiliser Spring 3 RC1. Je pars de l'hypothèse que vous utilisez maven pour le build de vos projets.
Pour commencer ajouter les URL des différents repository des Bundles dans votre settings.xml :

  <repository>
<id>com.springsource.repository.bundles.snapshot</id>
<name> SpringSource Enterprise Bundle Repository – SpringSource Bundle Snapshots </name>
<url>http://repository.springsource.com/maven/bundles/snapshot </url>
</repository>
<repository>
<id>com.springsource.repository.bundles.milestone </id>
<name> SpringSource Enterprise Bundle Repository – SpringSource Bundle Milestones </name>
<url> http://repository.springsource.com/maven/bundles/milestone </url>
</repository>
<repository>
<id>com.springsource.repository.bundles.release </id>
<name> SpringSource Enterprise Bundle Repository – SpringSource Bundle Releases </name>
<url> http://repository.springsource.com/maven/bundles/release </url>
</repository>
<repository>
<id>com.springsource.repository.bundles.external </id>
<name> SpringSource Enterprise Bundle Repository – External Bundle Releases </name>
<url> http://repository.springsource.com/maven/bundles/external </url>
</repository>
et puis ajouter les dépendances des modules que vous voulez utilisé, il faudra prendre en compte le changement des noms des différents ArtifacId qui désormais suivent tous le format suivant :

<dependency>   <groupId>org.springframework</groupId>   <artifactId>org.springframework.core</artifactId>   <version>3.0.0.BUILD-SNAPSHOT</version></dependency>

la version actuelle est 3.0.0.BUILD-SNAPSHOT.

Veuillez noté que l'artifact spring tout court n'existe plus.

Ressources

Arjen poutsma REST in Spring 3 post
Arjen Poutsma Blog
Juregen Hoeller Annotated Web MVC in Spring 2.5 post
Juergen Hoeller Blog
Spring Integration
Slim Tebourbi sur insideit.fr autour de Spring Expression Language
Spring JavaConfig Reference Guide
Rod Johnson lors de la SpringOne/2GX 2009
Les rencontres Spring 2009, ROO par Bruno Guedes
Les rencontres Spring 2009, Keynote par Adrian Colyer