<?xml version="1.0" encoding="UTF-8"?> <rss
version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
xmlns:media="http://search.yahoo.com/mrss/"
> <channel><title>Blog Xebia France &#187; Tests</title> <atom:link href="http://blog.xebia.fr/category/tests/feed/" rel="self" type="application/rss+xml" /><link>http://blog.xebia.fr</link> <description>J2EE, Agilité et SOA</description> <lastBuildDate>Wed, 08 Feb 2012 09:23:16 +0000</lastBuildDate> <language>fr</language> <sy:updatePeriod>hourly</sy:updatePeriod> <sy:updateFrequency>1</sy:updateFrequency> <generator>http://wordpress.org/?v=</generator> <copyright>CC BY-NC-ND 2.0 http://creativecommons.org/licenses/by-nc-nd/2.0/fr/</copyright> <managingEditor>blog-france@xebia.com (Xebia France)</managingEditor> <webMaster>blog-france@xebia.com (Xebia France)</webMaster> <ttl>1440</ttl> <image> <url>http://blog.xebia.fr/videos/xebia-podcast.png</url><title>Blog Xebia France</title><link>http://blog.xebia.fr</link> <width>144</width> <height>144</height> </image> <itunes:new-feed-url>http://blog.xebia.fr/feed/podcast/</itunes:new-feed-url> <itunes:subtitle>Les podcasts de Xebia France vous permettent de suivre l&#039;actualité autour de Java, de l&#039;agilité, des technologies Web et bien d&#039;autres. Xebia France est une entreprise spécialisée dans les technologies Java et JEE en environnement agi[...]</itunes:subtitle> <itunes:summary>Les podcasts de Xebia France vous permettent de suivre l&#039;actualité autour de Java, de l&#039;agilité, des technologies Web et bien d&#039;autres. Xebia France est une entreprise spécialisée dans les technologies Java et JEE en environnement agile.</itunes:summary> <itunes:keywords>Xebia, Java, JEE, SOA, Agile, Méthodes, Agiles</itunes:keywords> <itunes:category text="Technology" /> <itunes:category text="Technology"> <itunes:category text="Software How-To" /> </itunes:category> <itunes:category text="Technology"> <itunes:category text="Tech News" /> </itunes:category> <itunes:author>Xebia France</itunes:author> <itunes:owner> <itunes:name>Xebia France</itunes:name> <itunes:email>blog-france@xebia.com</itunes:email> </itunes:owner> <itunes:block>no</itunes:block> <itunes:explicit>no</itunes:explicit> <itunes:image href="http://blog.xebia.fr/videos/xebia-podcast.png" /> <item><title>Spring, Hibernate, DBUnit et Surefire &#8211; Parallélisez vos tests</title><link>http://blog.xebia.fr/2012/02/03/spring-hibernate-dbunit-et-surefire-parallelisez-vos-tests/</link> <comments>http://blog.xebia.fr/2012/02/03/spring-hibernate-dbunit-et-surefire-parallelisez-vos-tests/#comments</comments> <pubDate>Fri, 03 Feb 2012 06:17:38 +0000</pubDate> <dc:creator>Jean Helou</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[Tests]]></category> <category><![CDATA[dbunit]]></category> <category><![CDATA[Hibernate]]></category> <category><![CDATA[Spring]]></category> <category><![CDATA[surefire]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=10527</guid> <description><![CDATA[Les DAO (Data Access Object) ou repository des applications contiennent souvent de l&#8217;information importante sur la façon dont les données d&#8217;une base doivent être consultées. Cette information prend la forme d&#8217;une logique métier qui est encodée dans un ou plusieurs langages, souvent un langage déclaratif (SQL, HSQL, JPQL, etc.) et un langage impératif (Java, Groovy, [...]]]></description> <content:encoded><![CDATA[<p>Les DAO (<a
href="http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html" rel="nofollow">Data Access Object</a>) ou <a
href="http://martinfowler.com/eaaCatalog/repository.html" rel="nofollow">repository </a> des applications contiennent souvent de l&#8217;information importante sur la façon dont les données d&#8217;une base doivent être consultées. Cette information prend la forme d&#8217;une logique métier qui est encodée dans un ou plusieurs langages, souvent un langage déclaratif (SQL, HSQL, JPQL, etc.) et un langage impératif (Java, Groovy, Scala, etc.).<br
/> Tester cette logique d&#8217;accès polyglotte peut s&#8217;avérer complexe et lent car ce type de test se prète mal aux techniques classiques de mock et nécessite plutôt l&#8217;écriture de tests d&#8217;intégration qui chargent une partie du contexte réel d&#8217;exécution. Par conséquent, les tests de cette couche sont parfois délaissés, voire abandonnés.</p><p>Cet article se propose de vous montrer comment réaliser de tels tests, avec un niveau d&#8217;isolation suffisant pour la parallélisation dans un processus multithread, tout en essayant de trouver le meilleur compromis avec le temps d&#8217;exécution de chaque test. Ces tests sont présentés dans une configuration très classique utilisant Spring et JPA/Hibernate.<br
/> L&#8217;implémentation utilise une base HSQLDB et quelques bibliothèques pour faciliter l&#8217;écriture du code, en essayant de rester aussi léger que possible. Les tests sont isolés pour que vous puissiez activer l&#8217;exécution parallèle du plugin Surefire de Maven au niveau des classes de test. Vous pourrez facilement dériver l&#8217;implémentation nécessaire à isoler vos tests au niveau des méthodes si vous le souhaitez.</p><h3><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Introduction"></a>Introduction</h3><p>Un premier réflexe est de réaliser des tests contre une base de donnée existante. L&#8217;automatisation de ces tests  mène très rapidement à des problèmes de reproductibilité et d&#8217;isolation, en particulier quand les traitements testés effectuent des opérations d&#8217;écriture dans la base de données, mais également parce que la base utilisée est souvent une base de développement que les développeurs peuvent être amenés à faire évoluer de manière incontrôlée. La solution immédiate est de créer une copie du jeu de données initial et de le recharger dans la base avant chaque test. Cette problématique, et sa conséquence, la gestion des jeux de données ont amené la création d&#8217;outils tels que <a
href="http://www.dbunit.org/" rel="nofollow">DBUnit</a>.</p><p>L&#8217;utilisation d&#8217;une base de données centralisée, même si elle est spécifique aux tests, amène son propre lot de problèmes : que se passe-t-il si plusieurs développeurs jouent leurs tests en même temps ? Les uns vont insérer leur jeu de données pendant que les autres font une écriture ; les tests ne sont pas isolés. Il faut créer une base de données par développeur.</p><p>Deux options s&#8217;offrent alors, la première consiste à créer un schéma pour chacun sur le serveur. Cela amène des problèmes de latences réseaux, de charge du serveur et rend impossible le travail en cas de coupure du réseau. Il vaut mieux pencher pour la seconde option et créer une instance sur la machine de chaque développeur. Comme ce sont souvent des machines de bureautique avec des disques lents,   un SGBD complet qui n&#8217;est finalement presque pas utilisé et qui consomme de précieuses ressouces est mal venu, surtout lorsqu&#8217;il faut partager ces ressources avec un IDE, un serveur d&#8217;application, &#8230; Reste la possibilité d&#8217;utiliser une base de données embarquée comme HyperHSQL (HSQLDB), H2, Derby, etc. L&#8217;instance est créée par le processus du test et détruite lorsqu&#8217;il se termine. Il reste à y charger la structure des tables et le tour est joué.</p><p>Ces tests d&#8217;intégration joués séquentiellement peuvent encore s&#8217;avérer trop lent, surtout dans le cadre d&#8217;un <em>build</em> complet qui joue tous les tests. Il convient alors de les paralléliser.</p><h3><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Structureduprojet"></a>Structure du projet</h3><p>Partons d&#8217;un projet classique utilisant Spring et JPA/Hibernate, packagé avec maven. La structure de projet prend la forme suivante :</p><p><span
style="display: block; text-align: center"><img
src="http://blog.xebia.fr/wp-content/uploads/2012/02/structure_projet.png" style="border: 0px solid black" /></span><br
/> Dans le dossier <em>main</em>, on retrouve les entités du modèle de données, les DAOs avec respectivement une interface et une implémentation et le descripteur de l&#8217;unité de persistance JPA.</p><p>Dans le dossier <em>test</em>, les tests des deux DAOs, les fichiers de configuration XML Spring, un fichier de propriétés pour hsqldb et les deux jeux de données pour les tests au format XML.</p><h3><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Cr%C3%A9erlestestsavecspring%26dbunit"></a>Créer les tests avec spring &amp; dbunit</h3><h4><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Lecoded%27untest"></a>Le code d&#8217;un test</h4><h5><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-AddressDaoTest.java"></a>AddressDaoTest.java</h5><pre class="brush: java; gutter: true; title: ; notranslate">
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({&quot;applicationContext-test.xml&quot;})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
                         DataSetTestExecutionListener.class})
@DataSet(value = &quot;AddressDaoTest.xml&quot;)
public class AddressDaoTest {
    @Autowired
    private AddressDao addressDao;
    @Test
    public void testFindByAddressname() {
        Address user = addressDao.findByAddress(&quot;a&quot;);
        assertNotNull(user);
    }
    @Test
    public void testFindByAbsentAddressname() {
        Address user = addressDao.findByAddress(&quot;c&quot;);
        assertNull(user);
    }
}
</pre><p>Ce test est exécuté avec <code>SpringJunit4ClassRunner</code> qui créé le contexte Spring avec les fichiers indiqués dans l&#8217;annotation <code>@ContextConfiguration</code> et le met en cache.</p><p>La mise en cache est un détail important pour l&#8217;exécution de plusieurs classes de tests. Si vous avez plusieurs classes de tests, déclarant exactement la même annotation <code>@ContextConfiguration</code> qui sont lancées dans un même processus, seule la première aura besoin d&#8217;initialiser le contexte Spring. Les suivantes pourront réutiliser le contexte économisant ainsi : le parsing de la configuration, le scan du classpath à la recherche d&#8217;annotations ainsi que les diverses phases d&#8217;initialisations des beans utilisés dans le contexte.</p><p>L&#8217;annotation <code>@TestExecutionListener</code> fournie par spring-test permet d&#8217;enregistrer des classes qui peuvent se brancher dans le cycle de vie d&#8217;exécution d&#8217;un test (before test class, before test method, &#8230;). L&#8217;utilité des listeners utilisés ici est précisée un peu plus bas.</p><h4><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Laconfiguration"></a>La configuration</h4><p>Notre contexte de test contient les éléments standards d&#8217;un contexte Spring JPA/Hibernate mais quelques points méritent d&#8217;être notés pour que nous puissions revenir dessus.</p><h5><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Lad%C3%A9clarationdeladatasourcehsqldb"></a>La déclaration de la datasource hsqldb</h5><pre class="brush: xml; gutter: true; title: ; notranslate">
&lt;bean id=&quot;dataSource&quot; class=&quot;org.springframework.jdbc.datasource.DriverManagerDataSource&quot;&gt;
    &lt;property name=&quot;driverClassName&quot; value=&quot;${ds.driver=org.hsqldb.jdbcDriver}&quot;/&gt;
    &lt;property name=&quot;url&quot; value=&quot;${ds.url=jdbc:hsqldb:mem:test}&quot;/&gt;
    &lt;property name=&quot;username&quot; value=&quot;${ds.username=sa}&quot;/&gt;
    &lt;property name=&quot;password&quot; value=&quot;${ds.password=}&quot;/&gt;
&lt;/bean&gt;
</pre><h5><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Lad%C3%A9clarationdelafactoryJPA%2FHibernate"></a>La déclaration de la factory JPA/Hibernate</h5><pre class="brush: xml; gutter: true; title: ; notranslate">
&lt;bean id=&quot;jpaVendorAdapter&quot; class=&quot;org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter&quot;&gt;
    &lt;property name=&quot;databasePlatform&quot; value=&quot;${hibernate.dialect}&lt;&quot;/&gt;
&lt;/bean&gt;
&lt;bean id=&quot;emf&quot; class=&quot;org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean&quot;&gt;
    &lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot;/&gt;
    &lt;property name=&quot;persistenceUnitName&quot; value=&quot;pdbunit&quot;/&gt;
    &lt;property name=&quot;jpaVendorAdapter&quot; ref=&quot;jpaVendorAdapter&quot;/&gt;
    &lt;property name=&quot;jpaProperties&quot;&gt;
        &lt;props&gt;
            &lt;prop key=&quot;hibernate.dialect&quot;&gt;${hibernate.dialect}&lt;/prop&gt;
            &lt;prop key=&quot;hibernate.hbm2ddl.auto&quot;&gt;${hibernate.hbm2ddl.auto=update}&lt;/prop&gt;
        &lt;/props&gt;
    &lt;/property&gt;
&lt;/bean&gt;
</pre><h4><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Leslisteners"></a>Les listeners</h4><h5><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-DependencyInjectionTestExecutionListener"></a>DependencyInjectionTestExecutionListener</h5><p>L&#8217;utilisation de ce listener défini par l&#8217;API <a
href="http://static.springsource.org/spring/docs/current/spring-framework-reference/html/testing.html" rel="nofollow">spring-test</a> permet simplement d&#8217;utiliser les annotations d&#8217;injection de dépendance (<code>@Autowired</code>,<code>@Resource</code>,&#8230;) sur des éléments de la classe de test. Ainsi, dans le test de DAO vu précédemment l&#8217;instance du DAO sera injectée avant l&#8217;exécution du test.</p><h5><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-DataSetTestExecutionListener"></a>DataSetTestExecutionListener</h5><p>Ce listener est défini dans l&#8217;API <a
href="https://github.com/excilys/spring-dbunit" rel="nofollow">spring-dbunit</a> et permet l&#8217;utilisation de l&#8217;annotation <code>@Dataset</code> sur une classe ou sur une méthode de test.</p><p>Cette annotation utilise <a
href="http://www.dbunit.org/" rel="nofollow">dbunit</a> pour charger un jeu de données avant chaque test et pour le supprimer après chaque test.</p><p>Par défaut, cette annotation cherche un fichier nommé dataSet.xml dans le même package que la classe de test qui porte l&#8217;annotation. Il est possible de changer le nom du fichier, d&#8217;en charger plusieurs, bref de choisir son jeu de données.</p><h4><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Unpremierpalier"></a>Un premier palier</h4><p>À ce stade, nous avons donc des tests qui chargent leur jeu de données dans une base mémoire avant de s&#8217;exécuter. Isolés du monde extérieur ils ne risquent pas d&#8217;échouer à cause d&#8217;un changement involontaire par une autre personne, d&#8217;une perte de connexion réseau ou d&#8217;une autre exécution de la suite de test par un second développeur.</p><h3><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Parall%C3%A8liserlestestsdansmaven"></a>Parallèliser les tests dans maven</h3><p>En l&#8217;état actuel, les tests ne peuvent pas être exécutés en multi-thread au sein d&#8217;un même processus, ce que sait faire le plugin surefire de maven en charge de l&#8217;exécution des tests. Unitairement rapides, la contrainte de les exécuter en séquentiel empêche de profiter pleinement des processeurs multi-cores des ordinateurs modernes. Les tests ne sont utiles que si ils sont exécutés et ils ne sont exécutés que si ils sont suffisamment rapides.</p><h4><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Unebasededonn%C3%A9eparthread"></a>Une base de donnée par thread</h4><p>Ce qui fait échouer l&#8217;exécution en mode multi-thread, ce sont les collisions entre les différents jeux de données manipulés par dbunit (deux jeux de données chargés en même temps peuvent modifier le résultat de certaines requêtes), sans compter les opérations d&#8217;écritures potentielles des traitements testés. Il faudrait que chaque test dispose de sa propre base de donnée.</p><h5><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-ThreadUniqueDriverManagerDataSource"></a>ThreadUniqueDriverManagerDataSource</h5><pre class="brush: java; gutter: true; title: ; notranslate">
public class ThreadUniqueDriverManagerDataSource extends
  DriverManagerDataSource {
 @Override
 public String getUrl() {
  long tId = Thread.currentThread().getId();
  return super.getUrl() + tId;
 }
}
</pre><p>En utilisant cette surcharge de DriverManagerDataSource comme classe d&#8217;implémentation pour votre dataSource, chaque thread accédera à sa propre base de données mémoire.</p><pre class="brush: xml; gutter: true; title: ; notranslate">
&lt;bean id=&quot;dataSource&quot; class=&quot;fr.xebia.jdbc.datasource.ThreadUniqueDriverManagerDataSource&quot;&gt;
    &lt;property name=&quot;driverClassName&quot; value=&quot;${ds.driver=org.hsqldb.jdbcDriver}&quot;/&gt;
    &lt;property name=&quot;url&quot; value=&quot;${ds.url=jdbc:hsqldb:mem:test}&quot;/&gt;
    &lt;property name=&quot;username&quot; value=&quot;${ds.username=sa}&quot;/&gt;
    &lt;property name=&quot;password&quot; value=&quot;${ds.password=}&quot;/&gt;
&lt;/bean&gt;
</pre><p>Il reste un problème: le contexte Spring n&#8217;est chargé et initialisé qu&#8217;une seule fois, par conséquent Hibernate ne créé la structure des tables qu&#8217;une seule fois. Si nous tentions d&#8217;exécuter nos tests, le &laquo;&nbsp;premier&nbsp;&raquo; &#8211; celui dans lequel le contexte Spring est initialisé &#8211; réussirait. Les autres échoueraient en se plaignant que les tables nécessaires à l&#8217;insertion de leur jeu de données n&#8217;existent pas.</p><p>Il est possible de forcer le runner de Spring à réinitialiser un contexte pour chaque classe de test, mais cette initialisation est coûteuse et tout ce dont nous avons besoin est de créer les tables dans la base de données, tout le reste fonctionne très bien sans réinitialisation.</p><h4><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Recr%C3%A9erlastructuredelabasededonn%C3%A9esdanschaquethread."></a>Recréer la structure de la base de données dans chaque thread.</h4><p>Sachant que les listeners de test sont exécutés dans l&#8217;ordre de leur déclaration, il nous faut donc un listener qui soit exécuté après la création du contexte mais avant le chargement des données et qui se charge de recréer la structure. Nous avons déjà du code capable de recréer la structure dans Hibernate, il faut donc l&#8217;exécuter à nouveau pour chaque nouvelle base. La dernière donnée est le niveau de parallélisation, le maven-surefire-plugin est capable de paralléliser aux niveaux :  &laquo;&nbsp;classes&nbsp;&raquo;, &laquo;&nbsp;methods&nbsp;&raquo;, &laquo;&nbsp;both&nbsp;&raquo;. Il faudra adapter le listener en fonction du niveau choisi.</p><p>Dans la suite nous allons chercher à exécuter le code parallélisé au niveau &laquo;&nbsp;classes&nbsp;&raquo;, notre TestListener peut donc s&#8217;exécuter &laquo;&nbsp;beforeTestClass&nbsp;&raquo;.</p><h5><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-JpaHibernateDbSetupTestListener"></a>JpaHibernateDbSetupTestListener</h5><p>Le code du listener suivant est lié à l&#8217;utilisation JPA/Hibernate. Il ne devrait pas être très difficile de dériver un listener pour d&#8217;autres solutions de persistance, du moment que l&#8217;on sait créer la structure de la base de données.</p><pre class="brush: java; gutter: true; title: ; notranslate">
public class JpaHibernateDbSetupTestListener extends AbstractTestExecutionListener {
    @Override
    public void beforeTestClass(TestContext testContext) throws Exception {
        super.beforeTestMethod(testContext);
        Map&lt;String, EntityManagerFactoryInfo&gt; emfsMap = testContext.getApplicationContext().getBeansOfType(
                EntityManagerFactoryInfo.class);
        for (Map.Entry&lt;String, EntityManagerFactoryInfo&gt; emfByName : emfsMap.entrySet()) {
            EntityManagerFactoryInfo entityManagerFactoryInfo = emfByName.getValue();
            String puName = entityManagerFactoryInfo.getPersistenceUnitInfo().getPersistenceUnitName();
            HibernatePersistenceCacheUnit persistenceInformation = HibernatePersistenceCache.getPersistenceInformation(puName);
            SchemaExport schemaExport = new SchemaExport(persistenceInformation.getHibernateConfiguration(),
                    persistenceInformation.getSettings());
            schemaExport.execute(false, true, false, false);
        }
    }
}
</pre><p>Cette implémentation nécessite des modifications supplémentaires à notre environnement de test. Elle repose sur la présence d&#8217;un cache des informations de persistance (<code>HibernatePersistenceCache</code>) dans lequel sont stockées les informations nécessaires à la recréation du SchemaExport.</p><p>Les informations de configuration manipulées par SchemaExport ne sont normalement pas accessibles une fois l&#8217;initialisation d&#8217;Hibernate terminée. Il faut donc les capturer lors de l&#8217;initialisation et les ajouter au cache. Pour cela nous allons devoir remplacer l&#8217;adaptateur Spring <code>org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter</code> par une implémentation maison.</p><pre class="brush: java; gutter: true; title: ; notranslate">
public class HibernateJpaVendorParallelAdapter extends HibernateJpaVendorAdapter {
  private final PersistenceProvider persistenceProvider = new CachingHibernatePersistence();
  @Override
  public PersistenceProvider getPersistenceProvider() {
    return this.persistenceProvider;
  }
}
</pre><p>Le remplacement se fait dans le fichier de définition du contexte Spring, ce qui explique l&#8217;externalisation de la définition du jpaVendorAdapter :</p><pre class="brush: xml; gutter: true; title: ; notranslate">
&lt;bean id=&quot;jpaVendorAdapter&quot; class=&quot;fr.xebia.test.utils.parallel.jpa.hibernate.HibernateJpaVendorParallelAdapter&quot;&gt;
        &lt;property name=&quot;databasePlatform&quot; value=&quot;org.hibernate.dialect.Oracle10gDialect&quot;/&gt;
&lt;/bean&gt;
</pre><p>La capture des informations de configuration peut alors se faire dans <code>CachingHibernatePersistence</code> :</p><pre class="brush: java; gutter: true; title: ; notranslate">
public class CachingHibernatePersistence extends HibernatePersistence {
  @Override
  public EntityManagerFactory createEntityManagerFactory(String persistenceUnitName, Map overridenProperties) {
    Ejb3Configuration cfg = new Ejb3Configuration();
    Ejb3Configuration configured = cfg.configure(persistenceUnitName, overridenProperties);
    cacheConfig(persistenceUnitName, null, overridenProperties, configured);
    return configured != null ? configured.buildEntityManagerFactory() : null;
  }
  @Override
  public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) {
    Ejb3Configuration cfg = new Ejb3Configuration();
    Ejb3Configuration configured = cfg.configure(info, map);
    cacheConfig(info.getPersistenceUnitName(),info, map, configured);
    return configured != null ? configured.buildEntityManagerFactory() : null;
  }
  private void cacheConfig(String name, PersistenceUnitInfo info, Map map, Ejb3Configuration configured) {
    if (configured != null) {
      HibernatePersistenceCacheUnit cachedInfo = new HibernatePersistenceCacheUnit(name);
      cachedInfo.setPersistenceUnitInfo(info);
      cachedInfo.setPropertyMap(map);
      cachedInfo.setHibernateConfiguration(configured.getHibernateConfiguration());
      cachedInfo.setSettings(configured.buildSettings());
      HibernatePersistenceCache.addPersistenceInformation(cachedInfo);
    }
  }
}
</pre><p>HibernatePersistenceCacheUnit est un simple conteneur pour les données qui nous intéressent :</p><pre class="brush: java; gutter: true; title: ; notranslate">
public class HibernatePersistenceCacheUnit {
    private final String name;
    private PersistenceUnitInfo persistenceUnitInfo;
    private Map&lt;String, Object&gt; propertyMap;
    private Configuration hibernateConfiguration;
    private Settings settings;
    private SessionFactory sessionFactory;
    //...
}
</pre><p>et enfin le cache lui-même est une implémentation basique utilisant une simple HashMap :</p><pre class="brush: java; gutter: true; title: ; notranslate">
public class HibernatePersistenceCache {
  private static final Map&lt;String, HibernatePersistenceCacheUnit&gt; persistenceInformationMap = new HashMap&lt;String, HibernatePersistenceCacheUnit&gt;();
  public static void addPersistenceInformation(HibernatePersistenceCacheUnit persistenceInformation) {
    assert(persistenceInformation != null);
    assert(persistenceInformation.getName() != null);
    persistenceInformationMap.put(persistenceInformation.getName(), persistenceInformation);
  }
  public static List&lt;HibernatePersistenceCacheUnit&gt; getPersistenceInformations() {
    return new ArrayList&lt;HibernatePersistenceCacheUnit&gt;(persistenceInformationMap.values());
  }
  public static void clearPersistenceInformations() {
    persistenceInformationMap.clear();
  }
  public static HibernatePersistenceCacheUnit getPersistenceInformation(String name) {
    return persistenceInformationMap.get(name);
  }
}
</pre><p>Il ne reste plus qu&#8217;à déclarer le listener au bon endroit dans le test :</p><pre class="brush: java; gutter: true; title: ; notranslate">
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({&quot;applicationContext-test.xml&quot;})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, JpaHibernateDbSetupTestListener.class, DataSetTestExecutionListener.class})
@DataSet(value = &quot;AddressDaoTest.xml&quot;)
public class AddressDaoTest {
    @Autowired
    private AddressDao addressDao;
    @Test
    public void testFindByAddressname() {
        Address user = addressDao.findByAddress(&quot;a&quot;);
        assertNotNull(user);
    }
    @Test
    public void testFindByAbsentAddressname() {
        Address user = addressDao.findByAddress(&quot;c&quot;);
        assertNull(user);
    }
}
</pre><p>et à activer la parallélisation des tests lors de l&#8217;exécution du plugin surefire</p><pre class="brush: xml; gutter: true; title: ; notranslate">
&lt;plugin&gt;
    &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
    &lt;artifactId&gt;maven-surefire-plugin&lt;/artifactId&gt;
    &lt;version&gt;2.11&lt;/version&gt;
    &lt;configuration&gt;
        &lt;parallel&gt;classes&lt;/parallel&gt;
    &lt;/configuration&gt;
&lt;/plugin&gt;
</pre><p>Vos tests sont maintenant suffisament isolés pour que surefire puisse exécuter vos classes de tests en multi-thread.</p><h5><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Autrespistesdelisteners"></a>Autres pistes de listeners</h5><p>Il existe plusieurs moyens de résoudre le problème du rechargement de la structure de la base de données dans les bases créées en mémoire. Si vous disposez d&#8217;un outil de migration comme <a
href="http://code.google.com/p/flyway/">flyway</a>, ou que votre équipe garde scrupuleusement à jour un fichier avec la totalité de la structure de données de la base, vous pouvez créer votre propre listener pour charger la base à partir de cette référence. Vous pouvez également initialiser une base de données fichier plutôt que mémoire et en créer des copies qui seront utilisées par les différents Threads.</p><h3><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Desbasesdedonn%C3%A9esetdesdialectes"></a>Des bases de données et des dialectes</h3><p>Utiliser une base de donnée mémoire n&#8217;est pas complètement anodin. Chaque SGBD parle son propre &#8216;dialecte&#8217; de SQL, souvent un sous-ensemble auquel peut s&#8217;ajouter des extensions spécifiques de l&#8217;éditeur.<br
/> L&#8217;idée dans cet article est de maximiser les gains obtenus en testant les commandes SQL qui sont partagées entre votre SGBD de production et la base mémoire utilisée. L&#8217;utilisation de moteurs ORM comme Hibernate tends à normaliser le sous-ensemble de commandes utilisées, ce qui facilite la compatibilité pour la plupart des requêtes. Il peut néanmoins être nécessaire d&#8217;utiliser des requêtes natives pour certaines optimisations ou pour certains cas d&#8217;utilisation spécifiques. Même comme ça, les cas d&#8217;incompatibilités peuvent être réduit par l&#8217;utilisation d&#8217;une base mémoire adaptée. Recherchez la base mémoire qui a un dialecte aussi proche que possible de celui de votre moteur de production pour pouvoir tester autant de requêtes que possible.</p><p>Le code qui malgré tout restera incompatible pourra être testé dans le cadre de tests d&#8217;acceptation sur un environnement complet. Respectez la <a
href="http://fr.wikipedia.org/wiki/Loi_de_Pareto" rel="nofollow">loi de Pareto</a> en dépensant 20% d&#8217;effort pour 80% des gains, et ne modifiez pas une requête optimisée pour votre moteur de production juste pour pouvoir la tester dans un moteur de test.</p><h3><a
name="Spring%2CHibernate%2CDBUnitetSurefire-Parall%C3%A9lisezvostests-Codedel%27article"></a>Code de l&#8217;article</h3><p>Vous retrouverez sur github</p><ul><li>le <a
href="https://github.com/jeantil/spring-dbunit-utils-demo/" rel="nofollow">projet de démo</a> qui a servi de base au code de l&#8217;article</li><li>un <a
href="https://github.com/jeantil/spring-dbunit-utils" rel="nofollow">projet</a> contenant l&#8217;implémentation du listener et du cache sous forme de librairie maven.</li></ul><div
class="shr-publisher-10527"></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div><div
class='shareaholic-like-buttonset' style='float:none;height:30px;'><a
class='shareaholic-googleplusone' data-shr_size='medium' data-shr_count='true' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2012%2F02%2F03%2Fspring-hibernate-dbunit-et-surefire-parallelisez-vos-tests%2F' data-shr_title='Spring%2C+Hibernate%2C+DBUnit+et+Surefire+-+Parall%C3%A9lisez+vos+tests'></a><a
class='shareaholic-tweetbutton' data-shr_count='horizontal' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2012%2F02%2F03%2Fspring-hibernate-dbunit-et-surefire-parallelisez-vos-tests%2F' data-shr_title='Spring%2C+Hibernate%2C+DBUnit+et+Surefire+-+Parall%C3%A9lisez+vos+tests'></a></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div>]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2012/02/03/spring-hibernate-dbunit-et-surefire-parallelisez-vos-tests/feed/</wfw:commentRss> <slash:comments>5</slash:comments> </item> <item><title>Visuwall &#8211; Mixer vos outils de build et de qualité</title><link>http://blog.xebia.fr/2011/12/02/visuwall-mixer-vos-outils-de-build-et-de-qualite/</link> <comments>http://blog.xebia.fr/2011/12/02/visuwall-mixer-vos-outils-de-build-et-de-qualite/#comments</comments> <pubDate>Fri, 02 Dec 2011 10:00:09 +0000</pubDate> <dc:creator>Julien Smadja</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[Méthodes agiles]]></category> <category><![CDATA[Tests]]></category> <category><![CDATA[Bamboo]]></category> <category><![CDATA[Hudson]]></category> <category><![CDATA[intégration continue]]></category> <category><![CDATA[Jenkins]]></category> <category><![CDATA[Sonar]]></category> <category><![CDATA[TeamCity]]></category> <category><![CDATA[Visuwall]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=9633</guid> <description><![CDATA[Visuwall est un outil agrégeant et synthétisant les métriques fournies par Hudson, Jenkins, Bamboo, Teamcity et Sonar. Pour faire simple, imaginez que sur un seul écran vous puissiez voir en un clin d’œil l&#8217;état de vos builds (succès/instabilité/échec), le nombre de tests unitaires et d&#8217;intégration agrémenté de métriques telles le nombre de lignes de code, [...]]]></description> <content:encoded><![CDATA[<p><a
href="http://awired.github.com/visuwall/" title="official site" target="_blank">Visuwall</a> est un outil agrégeant et synthétisant les métriques fournies par <a
href="http://hudson-ci.org" rel="nofollow">Hudson</a>, <a
href="http://jenkins-ci.org" rel="nofollow">Jenkins</a>, <a
href="http://www.atlassian.com/software/bamboo" rel="nofollow">Bamboo</a>, <a
href="http://www.jetbrains.com/teamcity" rel="nofollow">Teamcity</a> et <a
href="http://www.sonarsource.org" rel="nofollow">Sonar</a>.</p><p>Pour faire simple, imaginez que sur un seul écran vous puissiez voir en un clin d’œil l&#8217;état de vos builds (succès/instabilité/échec), le nombre de tests unitaires et d&#8217;intégration agrémenté de métriques telles le nombre de lignes de code, la couverture de code et le respect des normes de codages.</p><p>Mieux qu&#8217;un long discours, nous vous proposons une courte vidéo de 3 minutes qui démontre la facilité avec laquelle vous pouvez démarrer facilement avec l&#8217;outil.</p><div
align="center"> <iframe
type="text/html" style="width: 400px; height: 300px" src="http://www.youtube.com/embed/KmK3T3v_8r8" frameborder="0"><br
/> </iframe></div><p>Si l&#8217;outil vous intéresse, il est <a
href="http://repo1.maven.org/maven2/net/awired/visuwall/visuwall-web/0.2.1/visuwall-web-0.2.1.war" rel="nofollow">disponible au téléchargement</a> et le code source est à votre disposition sur <a
href="https://github.com/awired/visuwall" rel="nofollow">github</a>.</p><p>Si vous désirez en savoir plus sur le développement de ce projet, nous vous proposons une rétrospective de ces derniers mois, en partant de la genèse du projet jusqu&#8217;à son état actuel.</p><h3><a
name="DRAFT-Visuwall-Mixervosoutilsdebuildetdequalit%C3%A9-Lagen%C3%A8se"></a>La genèse</h3><p>Visuwall est un projet imaginé et développé par deux Xebians : Arnaud Lemaire et Julien Smadja. L&#8217;idée a germé en janvier 2011 à l&#8217;occasion d&#8217;une mission axée sur l’amélioration de la testabilité d&#8217;un projet Java. Sa première étape fut d&#8217;écrire plusieurs dizaines de tests d&#8217;acceptance afin de valider le bon fonctionnement d&#8217;un webservice REST.</p><p>Les tests échouaient souvent et pouvaient durer plusieurs heures suivant l&#8217;environnement ciblé. Nous voulions connaître l&#8217;état de chaque environnement immédiatement et surtout, rendre l&#8217;information visible aux personnes extérieures. Le premier réflexe a bien sûr été de récupérer une unité centrale, un écran et d&#8217;installer un plugin Hudson capable d&#8217;afficher un « mur » de projets.</p><p>Ensuite, nous avons commencé à tenter de l&#8217;adapter graphiquement : changer la taille des polices, des blocs, cacher les informations non pertinentes, en afficher d&#8217;autres, etc. Un premier fork du plugin a donc été fait sur Github mais le cycle de développement n&#8217;était pas optimal : difficulté à tester, redéploiement du plugin, vérifications manuelles, etc.</p><p>La customisation du plugin n&#8217;étant pas viable, nous décidons de créer notre propre logiciel, adapté à nos besoins et ouvert à plusieurs outils. Visuwall était né&#8230; Enfin, ce nom n&#8217;est pas d&#8217;origine, le nom de code était Jbiniou puis JWall, ProjectWall et enfin, un matin, il fut baptisé officiellement Visuwall, une simple contraction des termes Visual et Wall.</p><p>Visuwall sera développé en parallèle de notre projet chez notre client, dans un cadre open source (Apache Software License 2), hébergé sur Github et codé en Java.</p><p>Technologiquement parlant : une base de données embarquée HsqlDb, une couche de persistence JPA2/Hibernate 3, une couche service Spring3/Spring MVC et une interface graphique Html/Javascript utilisant jQuery.</p><p>Rapidement, le projet est découpé en plusieurs modules, le core, l&#8217;interface web et les plugins. Il était très important que Visuwall soit capable d&#8217;être étendu par des plugins que nous avions d&#8217;abord catégorisés en deux groupes: les plugins des outils de builds (Hudson,  Jenkins) et les plugins des outils de qualité (Sonar). Comme l&#8217;écriture d&#8217;un plugin se doit d&#8217;être simple, le design d&#8217;une API publique a été amorcé. Il s&#8217;agit principalement de remplir au maximum le contrat imposé par le moteur de Visuwall. Par exemple, un plugin doit être capable de fournir la liste des projets, la durée de tel ou tel build, le nombre de tests, etc.</p><p>Après plusieurs mois de développement, dans des endroits insolites comme les Bus, Metro, Rer et autres salles d&#8217;attente, le projet a été présenté lors du XKE de Juin afin de recueillir les premiers avis de nos collègues Xebians et pourquoi pas, les inciter à installer cette version 0.1 chez leurs clients.</p><p>À la suite des premiers retours (bugs et améliorations) nous avons travaillé sur une refonte totale (notamment sur l&#8217;API des plugins).</p><h3><a
name="DRAFT-Visuwall-Mixervosoutilsdebuildetdequalit%C3%A9-Modularit%C3%A9"></a>Modularité</h3><p>Au fur et à mesure du développement, nous nous apercevons que certaines parties de notre code constituent à elles seules des projets à part entière. Nous avons donc déplacé ces projets dans un repository Github séparé. Ainsi, la communauté pourra se servir facilement de ce qui a été commencé pour Visuwall et utilisable dans un autre contexte. C&#8217;est notamment le cas des parties clientes de Bamboo, TeamCity, Hudson, Jenkins, Sonar (projet nommé <a
href="https://github.com/awired/clients" rel="nofollow">Clients</a>), mais également la partie validation des formulaires d&#8217;où naquit une librairie de validation (<a
href="https://github.com/awired/client-bean-validation" rel="nofollow">Client Bean Validation</a>) qui est une implémentation en Javascript de la JSR303 pour la validation coté client des objets du serveur.</p><p>Pour améliorer la modularité du projet nous travaillons actuellement sur une transformation des clients en bundle OSGI afin de pouvoir charger et décharger des plugins à la volée pendant que l&#8217;application tourne. Cette évolution nous permet à la fois de mieux appréhender le coté modularité de l&#8217;OSGI mais également d&#8217;apporter une plus grande flexibilité à l&#8217;application. À terme, l&#8217;ajout du moteur OSGI permettra de faire fonctionner toute l&#8217;application avec des modules customisables, remplaçables et extensibles.</p><h3><a
name="DRAFT-Visuwall-Mixervosoutilsdebuildetdequalit%C3%A9-L%27aventurecontinue"></a>L&#8217;aventure continue</h3><p>Notre travail sur Visuwall ne fait que commencer, nous avons encore des anomalies à corriger et des fonctionnalités à implémenter. Nous espérons avoir un maximum de retours de la part de la communauté pour pouvoir proposer un logiciel de qualité et utilisable dans le cadre de tous vos projets.</p><p>Visuwall a été présenté à CITCON 2011 devant quelques personnes dont un représentant de JetBrains. Nous allons prochainement prendre contact avec lui pour discuter des différentes améliorations que nous souhaiterions dans l&#8217;API REST de Teamcity !</p><div
class="shr-publisher-9633"></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div><div
class='shareaholic-like-buttonset' style='float:none;height:30px;'><a
class='shareaholic-googleplusone' data-shr_size='medium' data-shr_count='true' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2011%2F12%2F02%2Fvisuwall-mixer-vos-outils-de-build-et-de-qualite%2F' data-shr_title='Visuwall+-+Mixer+vos+outils+de+build+et+de+qualit%C3%A9'></a><a
class='shareaholic-tweetbutton' data-shr_count='horizontal' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2011%2F12%2F02%2Fvisuwall-mixer-vos-outils-de-build-et-de-qualite%2F' data-shr_title='Visuwall+-+Mixer+vos+outils+de+build+et+de+qualit%C3%A9'></a></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div>]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2011/12/02/visuwall-mixer-vos-outils-de-build-et-de-qualite/feed/</wfw:commentRss> <slash:comments>4</slash:comments> </item> <item><title>Xebia à Epita le 24 Mai</title><link>http://blog.xebia.fr/2011/05/23/xebia-a-epita-le-24-mai/</link> <comments>http://blog.xebia.fr/2011/05/23/xebia-a-epita-le-24-mai/#comments</comments> <pubDate>Mon, 23 May 2011 09:51:40 +0000</pubDate> <dc:creator>Nicolas Jozwiak</dc:creator> <category><![CDATA[Divers]]></category> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[Publications]]></category> <category><![CDATA[Tests]]></category> <category><![CDATA[Dette technique]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=7796</guid> <description><![CDATA[Comme chaque année, l’école Epita organise une semaine de conférences technologiques. Cette année la 23ème édition se déroulera du 23 au 27 Mai. Ce sera l’occasion pour les étudiants de découvrir les métiers de l’informatique et de bénéficier de retours d’expériences de professionnels. C’est à ce titre que j’interviendrai le Mardi 24 Mai lors d’une [...]]]></description> <content:encoded><![CDATA[<p><img
src="http://blog.xebia.fr/wp-content/uploads/2011/05/epita.png" alt="epita" title="epita" width="200" height="119" style="margin: 1em 1em 1em 1em; float: right;" /><br
/> Comme chaque année, l’école <a
href="http://www.epita.fr/" title="Epita" >Epita</a> organise une semaine de conférences technologiques. Cette année la 23ème édition se déroulera du 23 au 27 Mai. Ce sera l’occasion pour les étudiants de découvrir les métiers de l’informatique et de bénéficier de retours d’expériences de professionnels.</p><p>C’est à ce titre que j’interviendrai le Mardi 24 Mai lors d’une présentation sur la dette technique. Le but sera de sensibiliser les futurs diplômés aux conséquences d’une dette non gérée sur les projets logiciels. Nous étudierons notamment les mécanismes de la dette et des bonnes pratiques pour la résorber.</p><div
class="shr-publisher-7796"></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div><div
class='shareaholic-like-buttonset' style='float:none;height:30px;'><a
class='shareaholic-googleplusone' data-shr_size='medium' data-shr_count='true' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2011%2F05%2F23%2Fxebia-a-epita-le-24-mai%2F' data-shr_title='Xebia+%C3%A0+Epita+le+24+Mai'></a><a
class='shareaholic-tweetbutton' data-shr_count='horizontal' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2011%2F05%2F23%2Fxebia-a-epita-le-24-mai%2F' data-shr_title='Xebia+%C3%A0+Epita+le+24+Mai'></a></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div>]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2011/05/23/xebia-a-epita-le-24-mai/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>L&#8217;avocat du TDD</title><link>http://blog.xebia.fr/2011/04/15/lavocat-du-tdd/</link> <comments>http://blog.xebia.fr/2011/04/15/lavocat-du-tdd/#comments</comments> <pubDate>Fri, 15 Apr 2011 07:00:02 +0000</pubDate> <dc:creator>Simon Caplette</dc:creator> <category><![CDATA[Méthodes agiles]]></category> <category><![CDATA[Tests]]></category> <category><![CDATA[Craftsmanship]]></category> <category><![CDATA[TDD]]></category> <category><![CDATA[Test-Driven-Development]]></category> <category><![CDATA[XP]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=7426</guid> <description><![CDATA[Ceci est une fiction. Toute ressemblance avec des personnes existant ou ayant existé serait totalement fortuite. Palais de justice de Paris, lundi 21 mars. Une fois n&#8217;est pas coutume, des développeurs sont assis sur le banc des accusés! Ils sont inculpés de faire perdre des millions d&#8217;euros à nos chères compagnies du CAC40 en écrivant [...]]]></description> <content:encoded><![CDATA[<p></p><p><em>Ceci est une fiction. Toute ressemblance avec des personnes existant ou ayant existé serait totalement fortuite.</em></p><p>Palais de justice de Paris, lundi 21 mars.</p><p>Une fois n&#8217;est pas coutume, des développeurs sont assis sur le banc des accusés! Ils sont inculpés de faire perdre des millions d&#8217;euros à nos chères compagnies du CAC40 en écrivant du code voué à crouler sous son propre  poids.</p><p>Mr Tedd, praticien du Test Driven Development, est aujourd&#8217;hui jugé ainsi que quelques uns de ses collègues : Mr Whatever, Mr Guru, Mrs Clickaway et Mr Testafter.</p><p>Prévoyant et confiant, il sera défendu par son avocat : Maître Darrow. Ils ont convenu ensemble de la stratégie à adopter: il faudra pointer du doigt les mauvaises habitudes souvent utilisées et mettre en avant les bonnes pratiques qu&#8217;il a appliqué tout au long des projets qui lui ont été confiés.</p><h3><a
name="Laudienceestouverte"></a>L&#8217;audience est ouverte</h3><p>Le Président, Mr Beck, ouvre la séance en rappelant les chefs d&#8217;accusations aux jurés:</p><ul><li>Le code applicatif contient trop de bugs</li><li>Le code applicatif n&#8217;est pas testé</li><li>Le code applicatif est pour la plupart du temps incompréhensible et reste trop volumineux pour le nombre de fonctionnalités</li><li>La modification et l&#8217;amélioration du code applicatif existant est trop risqué à entreprendre</li></ul><p>La parole est à la défense. Maître Darrow, soucieux de capter l&#8217;attention de son audience, rappelle le contexte:</p><blockquote><p>Nous sommes en 2011. Dans les missions dans lesquelles mon client, Mr Tedd, intervient, écrire du code représente 80% de son temps. Ce code capture des règles métiers plus ou moins complexes et qui évoluent dans le temps. Visuellement son travail se traduit souvent par ce qu&#8217;on appelle un front end sur lequel un utilisateur tel que vous et moi pouvons interagir. Un bon exemple serait un site de réservation de billets de train au niveau Européen. Vous voyez donc, Messieurs et Mesdames les jurés que l&#8217;enjeu est de taille, l&#8217;erreur coûte chère et qu&#8217;elle n&#8217;est donc pas acceptable.</p><p>Aujourd&#8217;hui, je vais vous démontrer que mon client, Mr Tedd, adopte les pratiques adéquates pour rendre son code fiable, évolutif et pérenne.</p><p>Laissez-moi d&#8217;abord vous présenter quelques types classiques de développeurs. Si je n&#8217;enlève rien aux connaissances techniques et aux talents d&#8217;ingénieurs de ces personnes, je leur reprocherais cependant une attitude trop laxiste quant à la fiabilité et la pérennité du code qu&#8217;ils soumettent mais aussi une négligence au regard des méthodes qui permettraient d&#8217;améliorer considérablement la qualité globale des projets auxquels ils participent.</p><p>Permettez-moi de commencer par le cas de Mr Whatever.</p></blockquote><h3><a
name="MrWhatever"></a>Mr Whatever</h3><p>Tous les regards se tournent vers ce grand gaillard assis au bout du banc des accusés.</p><blockquote><p>Mr Whatever, si vous me permettez de vous citer, vous m&#8217;avez dit :</p><p><em>&laquo;&nbsp;Moi je fais juste confiance à mon code. Il compile c&#8217;est donc qu&#8217;il fonctionne</em>.&nbsp;&raquo;</p><p>Je pense que Mr Whatever n&#8217;est jamais resté assez longtemps dans les entreprises pour lesquelles il travaillait pour savoir que son code en production posait plus de problèmes qu&#8217;il n&#8217;en résolvait. Je rappelle pour les profanes dans l&#8217;assistance, que la production  est la plateforme exposée au grand public donc aux vrais utilisateurs. C&#8217;est  dans cet environnement que la charge sur le système se révèle soutenue et  que, potentiellement, tous les chemins possibles dans le code écrit sont  réalisés en concurrence !</p><p>Le problème de Mr Whatever est qu&#8217;il ne sait réellement si son code fonctionne que lors de la phase de tests intensifs. Pour lui, tester c&#8217;est la tâche de ce que l&#8217;on appelle l&#8217;équipe de QA, tiré de l&#8217;anglais Quality Assurance, et qui s&#8217;assure de la qualité du logiciel fournit. Si son code ne fonctionne pas, ils le lui indiqueront et il corrigera. On voit bien que sa responsabilité de fournir du code qui fonctionne lui échappe.</p></blockquote><p>L&#8217;avocat se tourne vers l&#8217;audience et continue :</p><blockquote><p>Nous ne nous attarderons pas plus sur le cas de Mr Whatever, il appartient à une génération un peu dépassée par les événements.</p></blockquote><p>Cette dernière phrase entraine de légers gloussements dans l&#8217;assistance.</p><h3><a
name="MrGuru"></a>Mr Guru</h3><p>Maître Darrow, après avoir jeté un rapide coup d’œil aux papiers étalés sur son bureau, se tourne vers la personne assise aux côtés de Mr Whatever.</p><blockquote><p>Passons au cas de Mr Guru. Cas intéressant puisqu&#8217;il représente l&#8217;archétype de l&#8217;informaticien auquel on accorde toute sa confiance !</p><p>Ses connaissances et son expérience en font un expert dans l&#8217;écriture du code. Cependant, il ne teste jamais ce qu&#8217;il écrit. Il n&#8217;introspecte pas. Perte de temps pour lui ! Il a écrit assez de plateformes pour savoir comment écrire la vôtre. Très bien. Nous aurons alors un code de qualité. Cette affirmation est correcte si votre équipe n&#8217;est formée que de Mr Guru ! Situation atypique s&#8217;il en est, et bon courage pour les batailles d&#8217;ego. Mais bon &#8230; passons.</p></blockquote><p>L&#8217;avocat lève le bras pointant l&#8217;index vers le plafond et lance :</p><blockquote><p>Si notre postulat de départ est que les entreprises ne travaillent qu&#8217;avec des profils tels que celui de Mr Guru, voici les problèmes que cela entrainerait :</p></blockquote><p>Maître Darrow marque un silence pour permettre aux jurés de bien se concentrer sur ce qui va suivre.</p><blockquote><p>Le premier problème serait une pénurie rapide de Mr Guru sur le marché. Toutes les entreprises ne pourraient pas en recruter et seraient dans l&#8217;incapacité de produire des applications de qualité. Un peu fantasque je vous l&#8217;accorde, mais totalement valide avec le postulat de départ.</p><p>Le deuxième problème impacterait le budget de l&#8217;entreprise. Un Mr Guru cela coûte cher ! Combien seriez-vous prêt à dépenser pour une armée de Mr Guru ?</p><p>Le dernier souci, et non des moindres, serait de s&#8217;assurer qu&#8217;on ne recrute que des Mr Guru. S&#8217;il y a un responsable des ressources humaines dans la salle, il m&#8217;accordera que la tâche se révélera ardue. Par ailleurs, je note aussi qu&#8217;il arrive à Mr Guru d&#8217;être fatigué ou d&#8217;être ennuyé, Guru ou pas, il reste humain. De ce fait, il est toujours possible qu&#8217;un expert introduise des erreurs dans votre code de production sans le vouloir.</p><p>De plus une question se pose. Faut-il bannir les développeurs juniors de votre équipe de développement ? Avons-nous peur qu&#8217;ils insèrent des erreurs dans le code ? Un bon manager vous dira que cela serait une faute. Une équipe doit être hétérogène en caractère. Pourquoi se couper du dynamisme des développeurs juniors, de la facilité avec laquelle ils manient les dernières technologies et de leur passion encore verte pour la programmation ?</p></blockquote><p>Après une longue pause, l&#8217;avocat fixe chaque membre du jury tour à tour, et conclut :</p><blockquote><p>Nous voyons bien qu&#8217;en réalité une équipe est constituée de profils différents et hétérogènes en compétence. Il nous faut donc une pratique commune de développement qui nous assure que Mr Guru et son collègue, plus junior, écrivent leur code d&#8217;une manière uniforme et testée sans porter atteinte à leur créativité.</p></blockquote><p>Dans la salle, quelques personnes opinèrent du chef. Sûrement des développeurs!</p><h3><a
name="MrsClickaway"></a>Mrs Clickaway</h3><p>L&#8217;avocat se rapproche de quelques pas du jury tout en désignant de la main la jeune femme assise au milieu du banc des accusés.</p><blockquote><p>Maintenant le cas touchant de Mrs Clickaway. Mrs Clickaway est bien intentionnée. Elle veut savoir si son code fonctionne. Pour cela elle adopte un cycle rapide d&#8217;écriture de code, et de déploiement de son application dans son environnement local. Pour tester, elle clique et re-clique jusqu&#8217;à ce qui lui semble avoir parcouru tous les recoins des fonctionnalités qu&#8217;elle a implémentées. Pourtant, Mrs Clickaway utilise très mal son temps de développement. Pour les plus techniciens d&#8217;entre vous, je dirais qu&#8217;elle oscille entre stacktrace, long démarrage de serveur et debuging en tous genres !</p><p>En effet, alors qu&#8217;elle aurait l&#8217;opportunité d&#8217;écrire son code tout en l&#8217;introspectant, grâce aux trés bonnes librairies logicielles mises à disposition aujourd&#8217;hui, elle choisit de tester en mode boite noire. Pour les profanes dans la salle, le mode boîte noire est une série de tests qui se fait à un haut niveau de l&#8217;application sans se préoccuper réellement de la structure et du fonctionnement interne.</p><p>Je reproche donc à Mrs Clickaway de ne pas se concentrer sur son coeur de métier qui est la production d&#8217;un code propre et fiable. Pour être franc, je pense que Mrs Clickaway n&#8217;est jamais sûre de ce qu&#8217;elle écrit et qu&#8217;elle veut se donner bonne conscience avant de livrer son code à l&#8217;exploitation.</p><p>Je vous le dis, ne pas choisir l&#8217;introspection, c&#8217;est ne pas choisir de réfléchir sur ce que l&#8217;on produit et c&#8217;est ne pas choisir l&#8217;amélioration de son propre code. Mesdames et Messieurs les jurés, vous m&#8217;accorderez que c&#8217;est un peu surprenant et relève d&#8217;un comportement irresponsable pour un développeur.</p></blockquote><p>Sur le banc des accusés Mrs Clickaway semble anéantie et confuse par cette série d&#8217;accusations.</p><h3><a
name="MrTestafter"></a>Mr Testafter</h3><blockquote><p>Passons maintenant à mon cas favori. Mr Testafter.</p><p>Mr Testafter est sur la voie de la rédemption, seulement il a pris à gauche alors qu&#8217;il fallait prendre à droite ! Que je vous explique. Mr Testafter a bien compris qu&#8217;il fallait tester son code. Cependant, il écrit les tests après avoir écrit ce fameux code de production. Normal vous me direz. Eh bien non ! Cela pose plusieurs problèmes:</p><p>Comme tous les développeurs, Mr Testafter passe beaucoup de temps à écrire son code. Quand il en a enfin fini, c&#8217;est un soupir de soulagement avec une pause café bien méritée. Enfin jusqu&#8217;à ce qu&#8217;il réalise qu&#8217;il n&#8217;a écrit aucun test ! Pour ne rien arranger, il a déjà indiqué fièrement à son manager que son travail était fini. Qu&#8217;à cela ne tienne, il va ajouter rapidement les tests, parce qu&#8217;après tout, il n&#8217;a pas que ça à faire.</p><p>C&#8217;est à ce moment qu&#8217;il doit faire un effort non négligeable: se remémorer les spécifications, imaginer des cas d&#8217;utilisations pour finalement&#8230; relire son code afin de savoir quoi tester !</p></blockquote><p>L&#8217;avocat lance un regard vers Mr Testafter sur le banc des accusés. Ce dernier fixe le plafond.</p><blockquote><p>Idéalement, s&#8217;il veut que ses tests soient utiles, c&#8217;est-à-dire compréhensibles par un autre développeur, clairs, découpés en spécifications et couvrant toutes les fonctionnalités, il doit reprendre sa réflexion à zéro.</p><p>Cependant, Mr Testafter à ce moment précis est pressé. Il n&#8217;adoptera pas une démarche d&#8217;écriture &laquo;&nbsp;fonctionnalité par fonctionnalité&nbsp;&raquo;. Plus grave, il ne pensera pas aux effets de bord, car puisque son code est déjà écrit, il est censé fonctionner ! Summum du contre-sens, pour les praticiens du TDD dans la salle, Mr Testafter ne passe pas par la phase d&#8217;un test qui échoue. En réalité, Mr Testafter ne voit aucun test échouer ! Bizarre pour quelqu&#8217;un qui veut savoir si son code fonctionne.</p><p>Une fois les tests ajoutés, laissez-moi vous décrire le résultat, cela se vérifie presque à chaque fois. Il y a toujours plus de code généré qu&#8217;il n&#8217;en faut et le code contient de la duplication. Mr Testafter n&#8217;est pas dans une optique de code minimal pourtant si importante dans les énormes bases de code d&#8217;aujourd&#8217;hui. Les tests sont désordonnés, ne sont pas organisés en terme de fonctionnalités et contiennent eux aussi de la duplication.</p><p>Invariablement aussi, une fois les tests ajoutés, le code restera tel qu&#8217;il a été écrit à l&#8217;origine. Pas d&#8217;optimisation, de minimalisation ou de recherche d&#8217;expressivité du code. Le plus grave cependant est d&#8217;obtenir ce que l&#8217;on appelle un effet gruyère sur la classe testée: des bouts de code resteront non testés. Pour ceux qui me suivraient encore dans l&#8217;assistance, je ne vous conseille pas de refactorer du gruyère !</p></blockquote><p>Sur cette dernière phrase, l&#8217;audience se partage entre face crédules et sourires.</p><h3><a
name="MrTeddVersuncodeagile"></a>Mr Tedd &#8211; Vers un code agile</h3><p>Il est l&#8217;heure maintenant pour Mr Darrow de défendre son client. Il tient à expliquer aux jurées les principes fondamentaux sur lesquelles repose la méthode que son client Mr Tedd utilise. Il enchaine alors :</p><blockquote><p>A travers les différents profils que je vous ai présenté, nous avons pu voir les insuffisances de certaines techniques de développement.</p><p>Laissez-moi donc maintenant vous résumer la manière d&#8217;écrire du code applicatif la plus à même à répondre à la problématique de tolérance au changement du monde du logiciel. C&#8217;est celle que pratique mon client. C&#8217;est le Test Driven Development. Cette méthodologie se résume avec la phrase suivante :</p><p><strong><em>Tout code de production a été écrit pour faire passer un test qui au départ échouait.</em></strong></p><p>Ce postulat pour certains peut sembler abstrait, cependant il se met en pratique tous les jours avec succès. Laissez-moi maintenant énumérer les corollaires directs de ce postulat.</p></blockquote><p>Dans la salle on aurait pu entendre une mouche voler. L&#8217;avocat commence à énoncer :</p><blockquote><p>Tout code de production est donc testé. Il n&#8217;y a pas plus de code que de fonctionnalités voulues et votre base de code est donc minimale. Toute fonctionnalité étant couverte par un test, vous savez instantanément lorsqu&#8217;un test échoue qu&#8217;une fonctionnalité n&#8217;est plus respectée et vous pouvez prendre aussitôt les mesures appropriées.</p><p>Ce code, proprement testé, vous pouvez le simplifier, l&#8217;améliorer et le nettoyer sans risque. C&#8217;est ce que l&#8217;on appelle le refactoring. Votre code devient par conséquent malléable, flexible et donc agile. C&#8217;est le moyen d&#8217;éviter que votre code ne s&#8217;effondre sous son propre poids !</p></blockquote><p>L&#8217;avocat continue :</p><blockquote><p>Mesdames et Messieurs les jurés, je vous le demande : Pour ceux qui connaissent le monde du logiciel, quel est le mot le plus important dans l&#8217;agilité?</p></blockquote><p>Au vu des visages dubitatifs du public, et de cette femme âgée assise au premier rang qui avait commencé à tricoter, Maître Darrow comprit qu&#8217;il aurait à apporter lui-même la réponse.</p><blockquote><p><strong>Le Feedback</strong> ﻿﻿! Voilà le concept le plus important à mettre en place. Le <em>Test Driven Development</em> vous permet de mettre en place la plus petite boucle de feedback du monde agile. Instantanément vous savez si votre code répond à vos exigences.</p></blockquote><p>Alors que Mr Darrow est au beau milieu de la défense de son client, le président Mr Beck, avec un geste d&#8217;impatience de la main, déclare :</p><blockquote><p>Merci Mr Darrow. Cependant, avant que vous poursuiviez, l&#8217;avocat de la partie civile souhaite interroger maintenant votre client Mr Tedd. Et exceptionnellement, je ly&#8217;autorise.</p></blockquote><p>Mr Darrow ne peut qu&#8217;abdiquer et Mr Tedd se dirige alors vers la barre pour être interrogé. C&#8217;est alors qu&#8217;un petit homme au front suintant, très agité, s&#8217;avance au centre. C&#8217;est Mr Pascal, l&#8217;avocat de la partie civile. Le contre interrogatoire peut commencer.</p><p><em>La suite du procès dans un prochain article.</em></p><div
class="shr-publisher-7426"></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div><div
class='shareaholic-like-buttonset' style='float:none;height:30px;'><a
class='shareaholic-googleplusone' data-shr_size='medium' data-shr_count='true' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2011%2F04%2F15%2Flavocat-du-tdd%2F' data-shr_title='L%27avocat+du+TDD'></a><a
class='shareaholic-tweetbutton' data-shr_count='horizontal' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2011%2F04%2F15%2Flavocat-du-tdd%2F' data-shr_title='L%27avocat+du+TDD'></a></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div>]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2011/04/15/lavocat-du-tdd/feed/</wfw:commentRss> <slash:comments>19</slash:comments> </item> <item><title>Performance &#8211; Maîtriser son framework de test, The Grinder</title><link>http://blog.xebia.fr/2011/03/31/performance-maitriser-son-framework-de-test-the-grinder/</link> <comments>http://blog.xebia.fr/2011/03/31/performance-maitriser-son-framework-de-test-the-grinder/#comments</comments> <pubDate>Thu, 31 Mar 2011 12:28:28 +0000</pubDate> <dc:creator>Issam El Fatmi</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[Performance]]></category> <category><![CDATA[Tests]]></category> <category><![CDATA[Performances]]></category> <category><![CDATA[The Grinder]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=7340</guid> <description><![CDATA[La performance a été souvent considérée comme étant le parent pauvre des applications. Afin de combler ce défaut et de détecter les éventuels points de faiblesse des applications, plusieurs outils propriétaires et open source ont vu le jour sur le marché: Compuware/Qaload, LoadRunner, OpenSTA, JMeter, etc, et notamment The Grinder. L’adoption de ce dernier fut [...]]]></description> <content:encoded><![CDATA[<p>La performance a été souvent considérée comme étant le parent pauvre des applications. Afin de combler ce défaut et de détecter les éventuels points de faiblesse des applications, plusieurs outils propriétaires et open source ont vu le jour sur le marché: Compuware/Qaload, LoadRunner, OpenSTA, JMeter, etc, et notamment <strong>The Grinder</strong>. L’adoption de ce dernier fut moins évidente que celle de son homologue côté Apache, du fait de l’absence de support et d’une interface GUI pour la définition, la configuration et le paramétrage des scripts ; ce manque de l’aspect « cliquodrome » a fait croire aux utilisateurs que l’outil est à mettre uniquement dans les mains d’un développeur python, et a également conduit à en dissimuler les talents.<br
/> Le but de cet article est de donner un ensemble de guidelines pour faire un meilleur usage de l’outil.</p><h3><a
name="LeframeworkTheGrinder"></a>Le framework The Grinder</h3><h4><a
name="CestquoiTheGrinder"></a>C&#8217;est quoi The Grinder ?</h4><p><a
title="The Grinder" href="http://grinder.sourceforge.net/">The Grinder</a> est un framework de test de charge « Jython-based scripting » écrit en Java. Il est open source (sous licence BSD) et  hébergé chez sourceForge.</p><p>Il permet de tester:</p><ul><li>Les serveurs web en mode HTTP /HTTPS.</li><li>Tout ce qui vit dans un serveur d’application (Web service SOAP/REST, EJB, JMS,…)</li><li>Les bases de données avec JDBC.</li><li>Les servers FTP, POP3, SMTP, LDAP.</li></ul><p>The Grinder s&#8217;exécute sur une plateforme supportant une version Java 1.4 ou supérieur.</p><p>Il peut être utilisé à des fins telles que :</p><ul><li>Test fonctionnel : contrôler le rendu de l’application par rapport au résultat attendu.</li><li>Test de charge : vérifier que l’application peut supporter une charge donnée.</li><li>Test de capacité : connaître la charge maximale supportée par l’application avant l’apparition d’erreurs</li><li>Test de stress : vérifier la stabilité de l’application.</li></ul><h4><a
name="Historique"></a>Historique</h4><p>The Grinder, « Le moulin », a été initialement développé pour les besoins du livre <a
title="J2EE Performance Testing with BEA WebLogic Server" href="http://grinder.sourceforge.net/links.html#book">J2EE Performance Testing with BEA WebLogic Server</a> par Paco Gómez et Peter Zadrozny.</p><p>Par la suite, Philip Aston a pris possession du code et l’a retravaillé pour en créer The Grinder 2.</p><p>C’est en Juillet 2003 que la première édition du livre fut publiée par Peter, Philippe et Ted Osborne, faisant ainsi un usage intensif de l’outil à sa deuxième version (The Grinder 2).</p><p>Peu après, Philip Aston commence à travailler sur The Grinder 3. Cette version offre de nombreuses nouvelles fonctionnalités, la plus importante est le Scripting Jython.</p><p><strong>Environnement de développement</strong></p><ul><li>Eclipse 3.6.</li><li>Apatana 3/Pydev : plugin eclipse pour le développement de projets python :<ul><li>Update Site : <a
title="httpdownloadaptanacomstudio3plugininstall" href="http://download.aptana.com/studio3/plugin/install">http://download.aptana.com/studio3/plugin/install</a></li></ul></li><li>Grinder Stone : plugin eclipse pour exécuter les scripts grinder. Il permet également de lancer des sessions de debugging.<ul><li>Update Site : <a
title="httpgrinderstonegooglecodecomsvnupdatesitexml" href="http://grinderstone.googlecode.com/svn/update/site.xml">http://grinderstone.googlecode.com/svn/update/site.xml</a></li></ul></li><li>Jython 2.2.1</li><li>Grinder 3.4</li></ul><h3><a
name="ArchitectureConceptiondesScrip"></a>Architecture &amp; Conception des Scripts</h3><h4><a
name="Architecture"></a>Architecture</h4><p>Il est important de comprendre l’architecture du framework,  ses différents composants et son mode fonctionnement.</p><p>Afin d’illustrer ce propos, je vous propose les deux schémas ci-dessous ;  l’un qui fait apparaitre les composants de base d’une plateforme d’injection d’un point de vue conceptuel, et l’autre illustrant l’architecture et les terminologies spécifiques à The Grinder.</p><p><span
style="text-decoration: underline;">Design View</span></p><div
style="text-align: center;"><a
href="http://blog.xebia.fr/wp-content/uploads/2011/03/Architecture.png"><img
class="alignnone size-medium wp-image-7346" title="Architecture" src="http://blog.xebia.fr/wp-content/uploads/2011/03/Architecture-300x173.png" alt="Architecture" width="300" height="173" /></a></div><p><span
style="text-decoration: underline;">The Grinder View</span></p><div
style="text-align: center;"><a
href="http://blog.xebia.fr/wp-content/uploads/2011/03/Architecture2.png"><img
class="alignnone size-medium wp-image-7345" title="Architecture2" src="http://blog.xebia.fr/wp-content/uploads/2011/03/Architecture2-300x165.png" alt="Architecture2" width="300" height="165" /></a></div><ul><li>La console contrôle les workers (les injecteurs) par le biais des agents et se charge d’agréger les résultats de test de l’ensemble des injecteurs de tous les agents.</li><li>Les agents sont en charge de piloter un ou plusieurs worker (généralement une instance « agent » est créé par JVM).</li><li>Les workers (grinder.processes) permettent d’injecter la charge au système à tester en démarrant le nombre de thread spécifié par la clé « grinder.threads » (les utilisateurs virtuels) et remontent les statistiques à la console. Chaque thread ainsi créé, exécute le script de test « grinder.runs » fois.</li></ul><h4><a
name="Conceptiondesscriptsetdesscnar"></a>Conception des scripts et des scénarios</h4><p>Le diagramme ci-dessous permet d’illustrer la séquence d’exécution d’un script grinder (gérée par le framework) couplée avec celle d’un scénario composé de deux tâches (gérée par le développeur) :</p><div
style="text-align: center;"><a
href="http://blog.xebia.fr/wp-content/uploads/2011/03/DiargrammeSéquence.png"><img
class="alignnone size-medium wp-image-7344" title="DiargrammeSéquence" src="http://blog.xebia.fr/wp-content/uploads/2011/03/DiargrammeSéquence-261x300.png" alt="DiargrammeSéquence" width="261" height="300" /></a></div><p>Généralement les scénarios fonctionnels à dérouler se différencient  en bout de chaîne, ou plus simplement dit, partagent un certain  nombre de transactions (Tâches) ; Ce qui fait apparaître le besoin de décomposer vos scénarios en un ensemble de tâches réutilisables.</p><p>Le lien entre deux tâches successives (récupérer l’output d’une tâche pour le faire passer en input de la tâche suivante) est réalisé par la requête suivante:</p><pre class="brush: python; title: ; notranslate">
HTTPPluginControl.getHTTPUtilities().getLastResponse()
</pre><p>Un scénario est donc défini par la succession d’un ensemble de tâches. Il encapsule la logique d’exécution de ses tâches (Scenario.run()).</p><p>Un « ScenarioAScript » permet la création et l’initialisation d’un « Scenario » composé de deux tâches « Task1 » et « Task2 ». Il est également responsable de la gestion de la rampe pour son scénario se traduisant généralement par une attente d’une durée égale au « pacingTime » (le temps d’attente entre le lancement de la première exécution de deux Threads successifs).</p><p>Un « Tir » constitue le script d’entrée pour The Grinder, il joue le rôle d’un orchestrateur et permet de répartir les threads sur l’ensemble des scénarios (affectation Thread / Scenario<strong>X</strong>Script) selon la configuration souhaitée.</p><p>L’exemple ci-dessous permet de répartir les threads sur 3 scénarios avec les proportions suivantes : le  Scenario<strong>A</strong>Script 50%, le Scenario<strong>B</strong>Script 25 % et le Scenario<strong>C</strong>Script 25 %</p><pre class="brush: python; title: ; notranslate">
from net.grinder.script.Grinder import grinder
scripts = [&quot;ScenarioAScript&quot;, &quot;ScenarioBScript&quot;, &quot;ScenarioCScript&quot;]
# Ensure modules are initialised in the process thread.
for script in scripts: exec(&quot;import %s&quot; % script)
def createTestRunner(script):
    exec(&quot;x = %s.TestRunner()&quot; % script)
    return x
class TestRunner:
    def __init__(self):
        tid = grinder.threadNumber
        if tid % 4 == 2:
            self.testRunner = createTestRunner(scripts[1])
        elif tid % 4 == 3:
            self.testRunner = createTestRunner(scripts[2])
        else:
            self.testRunner = createTestRunner(scripts[0])
    # This method is called for every run.
    def __call__(self):
        self.testRunner()
</pre><h3><a
name="Cequilfautsavoir"></a>Ce qu&#8217;il faut savoir</h3><h4><a
name="Rinitialisationdescookiesaulan"></a>Réinitialisation des cookies au lancement de chaque run</h4><p>Il faut savoir qu’à chaque run, The Grinder effectue un « cookie-reset » ; ce qui pourrait être très embêtant dans le cas où votre démarche de test s’inscrit dans la logique suivante:</p><ul><li>S’authentifier (création de la session, initialisation des cookies)</li><li>Boucler sur le scénario fonctionnel n fois</li><li>Se déconnecter</li></ul><p>Pour pallier ce problème, la solution la plus simple consiste à récupérer l’ensemble des cookies initialisés au démarrage de la session lors du premier run et les injecter par la suite dans les runs qui suivent comme suit :</p><pre class="brush: python; title: ; notranslate">
# récupérer l’objet HTTPClientContext pour le thread en cours au premier run.
threadContext = HTTPPluginControl.getThreadHTTPClientContext()
#récupération de l’ensemble des cookies au premier run
cookies=CookieModule.listAllCookies(threadContext)
      # récupérer l’objet HTTPClientContext pour le thread en cours.
      threadContext = HTTPPluginControl.getThreadHTTPClientContext()
      # Injection des cookies dans le header http du thread en cours
      for cookie in cookies:
          CookieModule.addCookie(cookie, threadContext)
</pre><p>Une deuxième solution est envisageable ; Il s’agit de désactiver la gestion automatique des cookies :</p><pre class="brush: python; title: ; notranslate">
HTTPPluginControl.getConnectionDefaults().useCookies = 0
</pre><p>et de créer son propre « handler » de cookie :</p><pre class="brush: python; title: ; notranslate">
# un « implements » à la python de l’interface «CookiePolicyHandler»
class MyCookiePolicyHandler(CookiePolicyHandler):
    # implement your own cookie acceptance policy.
    def acceptCookie(self, cookie, request, response):
        # traitement souhaité
        return 1
    # control the sending of cookies according to the matching rules
    # for the path, domain, protocol, etc
    def sendCookie(self, cookie, request):
        # traitement souhaité
        return 1
</pre><p>Par la suite, quelque part dans vos scripts, vous devrez faire un « set » du custom-handler :</p><pre class="brush: python; title: ; notranslate">
CookieModule.setCookiePolicyHandler(MyCookiePolicyHandler())
</pre><h4><a
name="HttpGETnonimplicitedesressourc"></a>Http/GET non implicite des ressources statiques</h4><p>Les requêtes http exécutées par le framework ne permettent pas de récupérer systématiquement les ressources statiques (cacheables par défaut) liées à la page demandée, comme les fichiers de type *.css, *.jpeg, *.gif, *.jar,… Il faut donc penser à les récupérer explicitement (par un http/GET du fichier en question) dans le cas où vous jugerez que cela pourrait avoir un impact sur les résultats de test.</p><h4><a
name="Gestiondesnbsprunsnbsp"></a>Gestion des « runs »</h4><p>A un certain moment, lors du développent de vos scripts,  vous auriez été tentés de gérer le nombre d’exécutions (grinder.runs) par vous-mêmes afin de contourner certains problèmes, notamment celui du cookie-reset précédemment évoqué. Je comprendrais que vous eussiez été plus à l&#8217;aise avec un « loop » et des « if » pour implémenter toute la logique d’exécution souhaitée, mais vous auriez sans doute constaté que les temps de réponse augmentaient d’une manière aberrante et proportionnelle au nombre de VUs (grinder.threads) ; ce qui est certainement lié à la contention des ressources et des threads dans vos « loop ». Je voulais donc en venir à la règle suivante: <strong>« Déléguer au framework ce qu’il a à faire reste un principe fondamental de son utilisation à ne pas négliger ». </strong></p><h4><a
name="Externalisationetcentralisatio"></a>Externalisation et centralisation de la configuration au niveau de grinder.properties</h4><p>Mettre toute la configuration de vos scripts dans le fichier grinder.properties vous permettra de regrouper l’ensemble des paramètres d’une manière centralisée: «  un seul fichier à maintenir ». Vous disposez nativement de la méthode «  grinder.getProperties ()«clé» pour récupérer les valeurs des différentes clés (pas besoin d’une classe utilitaire avec un java.util.Properties.load()).</p><h4><a
name="Gestiondesexceptions"></a>Gestion des exceptions</h4><p>N’hésitez pas à renforcer vos tests par des blocs de try / catch afin de passer le test précédemment exécuté ou encore celui qui est en cours d’exécution en « fail ».</p><p>Il faudra également distinguer les erreurs techniques liées au traitement de la requête par le serveur (serveur injoignable, http code = ! 200,…) des erreurs fonctionnelles liées à la logique du déroulement du scénario (jeu de données invalide, des inputs introuvables, rendu de la page incohérent,…).</p><p>Ci-dessous une manière de faire:</p><pre class="brush: python; title: ; notranslate">
import sys, traceback
request = Test(1, &quot;Basic request&quot;).wrap(HTTPRequest(url = &quot;http://localhost:7001&quot;))
     try:
        result = request.GET(&quot;index.html&quot;)
     except:
        # mark the test as a failure
        grinder.statistics.forLastTest.success = 0
        # tracer l’erreur d’origine:
        grinder.getLogger().error(&quot;Unexpected error: %s: %s: %s&quot; % (sys.exc_info()[0], sys.exc_info()[1], traceback.print_exc()))
        # throw new ExceptionTechnique
        raise ExceptionTechnique(« message d’erreur »)
     # Si le traitement dans le bloc try est exécuté
     # correctement, on vérifie que le status du dernier test est
     # OK, et que le code retour http == 200
     else:
        # récupérer l’objet Statistique pour le dernier test
        statisticsForTest = grinder.statistics.forLastTest
        # Si le test s’est terminé avec succès  et
        # http reponse code == 200
        if statisticsForTest.success and
           statisticsForTest.getLong(&quot;httpplugin.responseStatus&quot;)
           == 200:
            try:
               Dérouler votre checkList sur le rendu attendu
            except:
               # mark the test as a failure
               grinder.statistics.forLastTest.success = 0
               # tracer l’erreur d’origine:
               grinder.getLogger().error(&quot;Unexpected error: %s: %s: %s&quot; % (sys.exc_info()[0], sys.exc_info()[1], traceback.print_exc()))
               # throw new ExceptionFocntionnelle :
               raise ExceptionFonctionnelle(« message d’erreur »)
        # sinon : erreur lors du traitement de la requête
        else:
            # mark the test as a failure
            grinder.statistics.forLastTest.success = 0
            # throw new ExceptionTechnique
            raise ExceptionTechnique(« message d’erreur »)
</pre><h4><a
name="ParsingXMLetXPath"></a>Parsing XML et XPath:</h4><p>Le parsing xml de la réponse constitue une étape quasi-évidente pour dérouler votre check-list sur les éléments attendus et également pour récupérer les valeurs de certains d’entre eux ; ainsi, vous pourrez savoir s’il est possible de passer à l’étape suivante de votre scénario.</p><p>En terme de technologie adoptée, je vous laisse faire votre choix entre l’utilisation des APIs standards fournies avec le JDK (javax.xml.parsers, org.xml.sax, javax.xml.xpath) et l’API <a
title="vtd-xml" href="http://vtd-xml.sourceforge.net/">vtd-xml</a> qui est plus puissante et moins consommatrice (basée sur la technique <a
title="Virtual Token Descriptor" href="http://vtd-xml.sourceforge.net/VTD.html">Virtual Token Descriptor</a> (VTD))</p><h4><a
name="Accsconcurrentsauxressourcesnb"></a>Accès concurrents aux ressources « statiques »</h4><p>Si vous manipulez des variables statiques dans vos scripts python, vous êtes sûrement face à un problème d&#8217;accès concurrentiel du moment que vous avez plus d’un injecteur sur une même JVM (grinder.processes&gt;1).</p><p>On reconnaît ce cas de figure par le positionnement du mot clé « global » sur une variable précédemment déclarée dans le script, dans le but de pourvoir modifier sa valeur <strong>(accès en écriture)</strong> à l’intérieur d’une méthode :</p><pre class="brush: python; title: ; notranslate">
maVariable = 0
   def incrementMaVariable() :
      global maVariable
      maVariable += 1
</pre><p>Pour remédier à ce problème, la solution la plus évidente consiste clairement à éviter d’employer ce genre de pratique qui n’est pas en phase avec l’architecture distribuée de The Grinder.<br
/> Néanmoins si vous y tenez, il est possible d&#8217;utiliser la classe Lock du module threading.py:</p><pre class="brush: python; title: ; notranslate">
import threading
# récupérer un 	Object Lock pour la gestion d'accès concurrent à la ressource &quot; maVariable &quot;
lock = threading.Lock()
maVariable = 0
    def incrementMaVariable() :
       try:
           # will block if lock is already held
           lock.acquire()
           # lecture de la variable
           global maVariable
           # écriture de la variable
           maVariable += 1
       finally:
           # release lock
           lock.release()
</pre><h4><a
name="Nommagedestransactionsetunicit"></a>Nommage des transactions et unicité des identifiants de test</h4><p>Pensez à nommer vos transactions d’une manière homogène et ordonnée ; cela vous facilitera la lecture des résultats de test par la suite.<br
/> Le modèle suivant pourrait apporter un certain confort de lisibilité et de clarté : <em>SC-&lt;<strong>R</strong><strong>éférence du scénario</strong>&gt;-STEP-&lt;<strong>Numéro de la transaction</strong>&gt;)&lt;<strong>Libellé de la transaction</strong>&gt;</em></p><p>Prenons par exemple le scénario « consultation du profil », composé de trois transactions à savoir : login, consulter profil et logout.<br
/> Cela aurait donné quelque chose de ce genre lors de la définition des tests:</p><pre class="brush: python; title: ; notranslate">
request1 = Test(1, &quot;SC-CONSULTATION-PROFIL-STEP-1)Login&quot;)
request2 = Test(2, &quot;SC-CONSULTATION-PROFIL-STEP-2)Consultation du profil&quot;)
request3 = Test(3, &quot;SC-CONSULTATION-PROFIL-STEP-3)Logout&quot;)
</pre><p>Les identifiants de vos tests doivent être uniques dans un scope «grinder-project» ; cela vous permettra d’une part d’établir rapidement le lien entre le test id et son libellé, et d’autre part d’éviter toute confusion de transaction (un même identifiant pour deux transactions différentes, bien qu’il soit toujours possible de les distinguer par le libellé).</p><p>N’optez pas pour des variables statiques (avec un système d’incrémentation), vous risquez de vous replonger dans le problème d’accès concurrents à partir du moment où vous êtes dans le cas d’une configuration multi-process par JVM; à la limite gardez-les en dur.</p><h4><a
name="IntgrationdansleBuildPipeLine"></a>Intégration dans le Build PipeLine</h4><p>Il existe un plugin Hudson : <a
title="http://wiki.hudson-ci.org/display/HUDSON/Grinder+Plugin" href="http://wiki.hudson-ci.org/display/HUDSON/Grinder+Plugin">http://wiki.hudson-ci.org/display/HUDSON/Grinder+Plugin</a>:</p><p>Ce plugin permet d’exécuter les scripts « grinder » pendant le build et d’inclure les résultats de tests obtenus dans le rapport hudson.</p><h4><a
name="Gnrationdesrapportsgraphiques"></a>Génération des rapports graphiques</h4><p>Il existe principalement deux outils :</p><ul><li><a
title="Grinder Analyzer " href="http://track.sourceforge.net/analyzer.html|http://track.sourceforge.net/analyzer.html">Grinder Analyzer </a>: Permet de parser les logs grinder et de générer des rapports graphiques (au format html). L’utilisation de cet outil reste très simple et suffisante  pour un aperçu visuel des résultats lorsqu’il s’agit d’un environnement de test mono-agent et mono-worker. Cependant, il trouve rapidement ses limites face à des données  issues de plusieurs « agents » et « workers », ou également lorsqu’il s’agit de générer des graphiques de comparaison entre deux tests.<ul><li>Démo: <a
title="httptracksourceforgenetexamplereporthtml" href="http://track.sourceforge.net/example/report.html">http://track.sourceforge.net/example/report.html</a></li></ul></li><li><a
title="groundreport " href="http://ground.sourceforge.net/|http://ground.sourceforge.net">ground-report </a>: Produit un ensemble de rapports graphiques (rapport de synthèse, rapport individuel par test, rapport de comparaison entre plusieurs tests,…) au format pdf et à partir d’une base de données PostgreSQL. Il est également possible de produire des rapports au format rtf, xml  ou xhtml.<ul><li>Démo: <a
title="httpgroundsourceforgenetsampleshtml" href="http://ground.sourceforge.net/samples.html">http://ground.sourceforge.net/samples.html</a></li><li>Il se distingue par :<ul><li>La qualité de ses rapports.</li><li>Configuration rapide.</li><li>La sauvegarde des résultats de test en base de données garantissant ainsi d’avoir l’historique de l’ensemble tests.</li><li>Son « ToolKit » pour  les opérations d’injection des données « grinder » en base.</li></ul></li></ul></li></ul><h3><a
name="Leslimites"></a>Les limites</h3><ul><li>Ne produit pas nativement des graphiques.</li><li>Ne permet pas de simuler des sessions à partir d’adresses IP différentes, ce qui constitue  généralement un point très important lorsque le target system  est « load-balancé » (souvent le cas en production).</li><li>Manque du support.</li></ul><h3><a
name="Conclusion"></a>Conclusion</h3><p>Dans la mesure où les tests de performance s&#8217;imposent de plus en plus, comme c&#8217;est le cas actuellement pour les tests unitaires, les tests d&#8217;intégration et les tests fonctionnels qui ne cessent de se greffer dans nos projets, il me semble envisageable de compléter la stack de test en faisant d&#8217;eux une partie intégrante des projets; ce qui nous permettra de les faire constamment marier aux évolutions que subissent les applications.</p><p>Aujourd&#8217;hui on parle d&#8217;un test unitaire cassé, on pourrait bien parler d&#8217;un script de test de charge cassé.</p><p>Pour peu que l&#8217;on ait conçu et développé ses scripts proprement (sans pour autant frôler la limite de l&#8217;over-design), on gagne énormément en maintenabilité: modifier le comportement d&#8217;un script, c&#8217;est comme si l&#8217;on s&#8217;apprêtait à modifier une  méthode d&#8217;un service.</p><p>Le fait que The Grinder s&#8217;appuie sur du Jython (implémentation de Python en Java),  on garde toujours un même environnement d&#8217;exécution; que ce soit du .java  vers du .class ou du .py vers $py.class jusqu&#8217;au runtime, c&#8217;est toujours la JVM qui bosse. En plus, vous continuez à profiter de toutes les API Java; ce qui vous offre le moyen de rester dans vos habitudes et de ne pas forcément glisser dans une démarche full-python.</p><div
class="shr-publisher-7340"></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div><div
class='shareaholic-like-buttonset' style='float:none;height:30px;'><a
class='shareaholic-googleplusone' data-shr_size='medium' data-shr_count='true' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2011%2F03%2F31%2Fperformance-maitriser-son-framework-de-test-the-grinder%2F' data-shr_title='Performance+-+Ma%C3%AEtriser+son+framework+de+test%2C+The+Grinder'></a><a
class='shareaholic-tweetbutton' data-shr_count='horizontal' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2011%2F03%2F31%2Fperformance-maitriser-son-framework-de-test-the-grinder%2F' data-shr_title='Performance+-+Ma%C3%AEtriser+son+framework+de+test%2C+The+Grinder'></a></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div>]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2011/03/31/performance-maitriser-son-framework-de-test-the-grinder/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>Tester les services asynchrones avec Awaitility</title><link>http://blog.xebia.fr/2011/03/23/tester-les-services-asynchrones-avec-awaitility/</link> <comments>http://blog.xebia.fr/2011/03/23/tester-les-services-asynchrones-avec-awaitility/#comments</comments> <pubDate>Wed, 23 Mar 2011 11:12:03 +0000</pubDate> <dc:creator>Julien Smadja</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[Tests]]></category> <category><![CDATA[Awaitility]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=7289</guid> <description><![CDATA[Les tests d&#8217;intégration impliquent souvent plusieurs composants d&#8217;une architecture technique (webservices, serveurs de mail, …). Si une action s&#8217;exécute sur un composant A qui fait appel à un composant B et si la condition à vérifier dépend de la bonne exécution de B, vous êtes dans un cas d&#8217;asynchronisme. La première idée qui vient à [...]]]></description> <content:encoded><![CDATA[<p>Les tests d&#8217;intégration impliquent souvent plusieurs composants d&#8217;une architecture technique (webservices, serveurs de mail, …). Si une action s&#8217;exécute sur un composant A qui fait appel à un composant B et si la condition à vérifier dépend de la bonne exécution de B, vous êtes dans un cas d&#8217;asynchronisme. La première idée qui vient à l&#8217;esprit est d&#8217;utiliser un timer, de mettre en pause l&#8217;exécution du test pour laisser le temps à l&#8217;action de se réaliser. On comprend très vite qu&#8217;une optimisation est à portée de main, et si au lieu d&#8217;attendre un temps constant on pouvait gagner du temps si l&#8217;action s&#8217;est réalisée plus rapidement que prévu.</p><p>Nous verrons dans cet article qu&#8217;il est possible de développer des tests automatisés capables de s&#8217;adapter facilement aux contraintes d&#8217;asynchronismes et de se passer d&#8217;un blocage à temps constant grâce à l&#8217;API <a
href="http://code.google.com/p/awaitility/" title="Awaitility" >Awaitility</a>.</p><p>Awaitility est une API open source créée en juillet 2010 pour répondre aux problématiques de tests de systèmes asynchrones. Utilisable sous forme de DSL, elle permet de simplifier les opérations qui nécessitaient auparavant la gestion de Threads, de Time Out et de concurrence d&#8217;accès. Awaitility est disponible aussi bien pour Java que pour Groovy et Scala. Le projet est actuellement dans sa version 1.3.1 et a été utilisé dans notre mission pour valider le comportement de deux serveurs qui communiquaient entre eux.</p><p>Si vous utilisez Maven, vous pouvez ajouter la dépendance suivante dans votre pom.xml</p><pre class="brush: xml; title: ; notranslate">
&lt;dependency&gt;
    &lt;groupId&gt;com.jayway.awaitility&lt;/groupId&gt;
    &lt;artifactId&gt;awaitility&lt;/artifactId&gt;
    &lt;version&gt;1.3.1&lt;/version&gt;
    &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
</pre><h3><a
name="Unexempledetest"></a>Un exemple de test</h3><p>Imaginons un service chargé de publier des annonces sur des panneaux publicitaires.</p><p>Vous êtes un annonceur et vous souhaitez publier une annonce sur l&#8217;ensemble des panneaux publicitaires d&#8217;un réseau. Un service est mis à votre disposition, voici l&#8217;interface :</p><pre class="brush: java; title: ; notranslate">
public interface IServicePanneauxPublicitaires {
    /**
     * Propage l'annonce sur tous les panneaux publicitaires La propagation peut durer jusqu'à une minute.
     * Note : Cet appel est non-bloquant
     * @param annonce
     *            Annonce à afficher sur les panneaux publicitaires
     */
    void publierAnnonce(String annonce);
    /**
     * @return Retourne la liste des panneaux publicitaires
     */
    List&lt;PanneauPublicitaire&gt; getPanneauxPublicitaires();
}
</pre><p>Comme on peut le remarquer, lorsque l&#8217;utilisateur de ce service va demander la publication de son annonce, il n&#8217;est pas assuré qu&#8217;elle sera immédiatement affichée sur l&#8217;ensemble du parc de panneaux publicitaires.</p><p>Si on désire tester que les panneaux affichent bien l&#8217;annonce en question, il faut être capable de laisser une minute (au maximum !) au service pour qu&#8217;il puisse faire son travail.</p><p>Le test ci-dessous montre comment tirer partie des possibilités d&#8217;Awaitility pour construire notre test d&#8217;intégration.</p><pre class="brush: java; title: ; notranslate">
@Test
/**
 * Scenario de test :
 * - publier une annonce via le service IServicePanneauxPublicitaires
 * - vérifier pour chacun des panneaux que l'annonce est publiée
 * - contrainte temporelle : la propagation doit durer moins d'une minute
 */
public void should_propagate_annonce_to_all_panneaux_publicitaires_within_one_minute() throws Exception {
    // récupération d'une implémentation du service de gestion des panneaux publicitaires
    IServicePanneauxPublicitaires servicePanneauxPublicitaires = new ServicePanneauxPublicitaires();
    // Publication d'une annonce
    // Rend la main à la méthode appelante mais peut mettre jusqu'à
    // une minute pour publier l'annonce sur tous les panneaux
    servicePanneauxPublicitaires.publierAnnonce(&quot;Fin des soldes dans 2 jours!&quot;);
    // Vérification de la publication de l'annonce sur les panneaux
    Callable&lt;Boolean&gt; condition = isAnnoncePropageeSurTousLesPanneaux(servicePanneauxPublicitaires, &quot;Fin des soldes dans 2 jours!&quot;);
    Awaitility.await().atMost(Duration.ONE_MINUTE).until(condition);
}
</pre><p>Intéressons nous tout d&#8217;abord à la dernière ligne car c&#8217;est elle qui porte toute la logique du test.</p><pre class="brush: java; title: ; notranslate">
Awaitility.await().atMost(Duration.ONE_MINUTE).until(condition);
</pre><p>En français elle signifie : La condition doit être vérifiée (return true) en moins d&#8217;une minute, sinon le test échoue.</p><p>La condition en question est un Callable&lt;Boolean&gt; qui contient les éléments de test à valider :</p><pre class="brush: java; title: ; notranslate">
private Callable&lt;Boolean&gt; isAnnoncePropageeSurTousLesPanneaux(final IServicePanneauxPublicitaires servicePanneauxPublicitaires, final String annonce) {
    return new Callable&lt;Boolean&gt;() {
        public Boolean call() throws Exception {
            // pour chaque panneau publicitaire
            List&lt;PanneauPublicitaire&gt; panneauxPublicitaires = servicePanneauxPublicitaires.getPanneauxPublicitaires();
            for (PanneauPublicitaire panneauPublicitaire : panneauxPublicitaires) {
                // on vérifie que l'annonce a été publiée
                String annonceCourante = panneauPublicitaire.getAnnonce();
                if (!annonce.equals(annonceCourante)) {
                    return false;
                }
            }
            // l'annonce a été propagée sur tous les tableaux
            return true;
        }
    };
}
</pre><p>L&#8217;algorithme est simple, pour que le test soit valide, il faut que l&#8217;annonce de chaque panneau publicitaire soit égale à l&#8217;annonce passée en paramètre. Si au moins un des panneaux n&#8217;a pas la bonne annonce, on retourne <strong>false</strong>. Si tous les panneaux ont la bonne annonce, on retourne <strong>true</strong>.</p><h3><a
name="FonctionnementdAwaitility"></a>Fonctionnement d&#8217;Awaitility</h3><p>Comment se déroule l&#8217;exécution du test <strong>should_propagate_annonce_to_all_panneaux_publicitaires_within_one_minute</strong> ? Awaitility appelle en boucle la méthode <em>isAnnoncePropageeSurTousLesPanneaux()</em> tant que celle-ci retourne <strong>false</strong> ou que le délai maximum fixé à une minute n&#8217;est pas atteint.</p><p>L&#8217;intérêt d&#8217;Awaitility réside dans sa capacité à continuer l&#8217;exécution du test dès que la condition est vérifiée. Dans notre cas, si tous les panneaux publicitaires ont été mis à jour en 5 secondes, il n&#8217;est pas nécessaire de bloquer le test pendant 1 minute. Par contre, dans le pire des cas, il faudra attendre le time out avant que le test passe en échec.</p><p>Mais attention, quand on dit « Awaitility appelle en boucle », il faut comprendre que <u>le callable sera exécuté toutes les 100 ms</u> (valeur par défaut). Cette valeur est modifiable et il est conseillé de la changer si nous ne voulons pas stresser les systèmes à tester.</p><p>On désire par exemple appeler le service qu&#8217;une fois par seconde, c&#8217;est ce qu&#8217;Awaitility appelle le <strong>pollInterval</strong>. On peut fixer le <strong>pollInterval</strong> pour l&#8217;ensemble des tests :</p><pre class="brush: java; title: ; notranslate">
Awaitility.setDefaultPollInterval(Duration.ONE_SECOND);
Awaitility.await().atMost(Duration.ONE_MINUTE).until(condition);
</pre><p>Ou simplement pour le test en cours :</p><pre class="brush: java; title: ; notranslate">
Awaitility.await().atMost(Duration.ONE_MINUTE).pollInterval(Duration.ONE_SECOND).until(condition);
</pre><p>De la même manière, on peut modifier le temps d&#8217;attente avant que la condition de test soit exécutée pour la première fois. Dans notre cas, on peut laisser 5 secondes d&#8217;avance au service de propagation des annonces avant de commencer à l&#8217;interroger.</p><pre class="brush: java; title: ; notranslate">
Awaitility.await().atMost(Duration.ONE_MINUTE).pollDelay(Duration.FIVE_SECONDS).until(condition);
</pre><p>Awaitility permet d&#8217;associer tous ces paramètres grâce aux méthodes and() et with(). Voici l&#8217;exemple complet (pollInterval + pollDelay) en utilisant les imports statiques pour faciliter la lisibilité :</p><pre class="brush: java; title: ; notranslate">
await().atMost(ONE_MINUTE).pollInterval(ONE_HUNDRED_MILLISECONDS).with().pollDelay(TWO_SECONDS).until(condition);
</pre><p>La condition à valider sera vérifiée au bout de 2 secondes et ensuite toutes les 100 millisecondes.</p><style>table {border: hidden; border-collapse: collapse; font-size: small;}
tr, th, td {border: dotted 1px #6A205F; padding: 5px;}
th {background: #F0EDF1;}</style><h3><a
name="DureetUnitdetemps"></a>Durée et Unité de temps</h3><p>Comme nous l&#8217;avons vu, Awaitility propose quelques paramètres de temps bien pratiques, voici la liste exhaustive des Duration que vous pouvez utiliser en paramètre de la méthode public ConditionFactory atMost(Duration timeout) :</p><table><tr><th> Duration</th><th> Temps</th></tr><tr><td> FOREVER</td><td> 8</td></tr><tr><td> ONE_HUNDRED_MILLISECONDS</td><td> 100 ms</td></tr><tr><td> TWO_HUNDRED_MILLISECONDS</td><td> 200 ms</td></tr><tr><td> FIVE_HUNDRED_MILLISECONDS</td><td> 500 ms</td></tr><tr><td> ONE_SECOND</td><td> 1 s</td></tr><tr><td> TWO_SECONDS</td><td> 2 s</td></tr><tr><td> FIVE_SECONDS</td><td> 5 s</td></tr><tr><td> TWO_MINUTES</td><td> 2 mn</td></tr><tr><td> FIVE_MINUTES</td><td> 5 mn</td></tr><tr><td> TEN_MINUTES</td><td> 10 mn</td></tr></table><p>Vous pouvez bien sûr configurer plus finement le timeout en utilisant la méthode <em>public ConditionFactory atMost(long timeout, TimeUnit unit)</em>. Pour rappel, les valeurs de <em>java.util.concurrent.TimeUnit</em> sont :</p><table><tr><th> TimeUnit</th><th> Temps</th></tr><tr><td> NANOSECONDS</td><td> 1/1000 000 000 s</td></tr><tr><td> MICROSECONDS</td><td> 1/1000 000 s</td></tr><tr><td> MILLISECONDS</td><td> 1/1000 s</td></tr><tr><td> SECONDS</td><td> 1 s</td></tr><tr><td> MINUTES</td><td> 1 mn</td></tr><tr><td> HOURS</td><td> 1 h</td></tr><tr><td> DAYS</td><td> 1 j</td></tr></table><p>Voici un exemple d&#8217;utilisation du TimeUnit pour une configuration plus fine des time outs.</p><pre class="brush: java; title: ; notranslate">
// attendre au maximum 3 minutes
await().atMost(3, MINUTES).until(condition);
</pre><h3><a
name="Conclusion"></a>Conclusion</h3><p>Les possibilités d&#8217;Awaitility ne s&#8217;arrêtent pas là et vous trouverez de plus amples informations en consultant le <a
href="http://code.google.com/p/awaitility/" title="site officiel" >site officiel</a> et la <a
href="http://www.jarvana.com/jarvana/view/com/jayway/awaitility/awaitility/1.3.1/awaitility-1.3.1-javadoc.jar!/index.html" title="Javadoc" >Javadoc</a> qui contient des <a
href="http://www.jarvana.com/jarvana/view/com/jayway/awaitility/awaitility/1.3.1/awaitility-1.3.1-javadoc.jar!/com/jayway/awaitility/Awaitility.html" title="exemples complets" >exemples complets</a>.</p><div
class="shr-publisher-7289"></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div><div
class='shareaholic-like-buttonset' style='float:none;height:30px;'><a
class='shareaholic-googleplusone' data-shr_size='medium' data-shr_count='true' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2011%2F03%2F23%2Ftester-les-services-asynchrones-avec-awaitility%2F' data-shr_title='Tester+les+services+asynchrones+avec+Awaitility'></a><a
class='shareaholic-tweetbutton' data-shr_count='horizontal' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2011%2F03%2F23%2Ftester-les-services-asynchrones-avec-awaitility%2F' data-shr_title='Tester+les+services+asynchrones+avec+Awaitility'></a></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div>]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2011/03/23/tester-les-services-asynchrones-avec-awaitility/feed/</wfw:commentRss> <slash:comments>2</slash:comments> </item> <item><title>Automatiser les tests Selenium avec Maven</title><link>http://blog.xebia.fr/2011/02/18/automatiser-les-tests-selenium-avec-maven/</link> <comments>http://blog.xebia.fr/2011/02/18/automatiser-les-tests-selenium-avec-maven/#comments</comments> <pubDate>Fri, 18 Feb 2011 09:33:36 +0000</pubDate> <dc:creator>Romain Schlick</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[Tests]]></category> <category><![CDATA[ant]]></category> <category><![CDATA[Automatisation]]></category> <category><![CDATA[dbunit]]></category> <category><![CDATA[failsafe]]></category> <category><![CDATA[HSQLDB]]></category> <category><![CDATA[intégration continue]]></category> <category><![CDATA[Jetty]]></category> <category><![CDATA[Maven]]></category> <category><![CDATA[Selenium]]></category> <category><![CDATA[Selenium IDE]]></category> <category><![CDATA[Selenium RC]]></category> <category><![CDATA[SQL]]></category> <category><![CDATA[surefire]]></category> <category><![CDATA[Tests d'intégration]]></category> <category><![CDATA[Tests fonctionnels]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=7049</guid> <description><![CDATA[Selenium regroupe une suite d&#8217;outils permettant de tester des applications web. Tout comme les tests unitaires, Selenium permet notamment de vérifier la non-régression d&#8217;une application et est un gage de qualité supplémentaire. Bien que la création des tests Selenium soit relativement simple, automatiser leur exécution sur un serveur d&#8217;intégration continue reste complexe à mettre en [...]]]></description> <content:encoded><![CDATA[<p><img
src="http://blog.xebia.fr/wp-content/uploads/2011/02/selenium.png" border="0" alt="selenium" style="margin: 1em 1em 1em 1em; float: right;" /></p><p><a
href="http://seleniumhq.org/" title="Selenium" >Selenium</a> regroupe une suite d&#8217;outils permettant de tester des applications web. Tout comme les tests unitaires, Selenium permet notamment de vérifier la non-régression d&#8217;une application et est un gage de qualité supplémentaire. Bien que la création des tests Selenium soit relativement simple, automatiser leur exécution sur un serveur d&#8217;intégration continue reste complexe à mettre en œuvre. Je vous propose une solution avec l&#8217;outil de build <a
href="http://maven.apache.org/" title="Maven" >Maven</a>. Disposant de nombreux plugins, comme SQL, Failsafe, Jetty et Selenium, Maven permet la mise en place d&#8217;une automatisation satisfaisante. Cette solution peut servir aussi aux tests d&#8217;intégration.</p><h3><a
name="Seleniumunpuissantoutilpourcre"></a>Selenium, un puissant outil pour créer des tests fonctionnels</h3><p>Les tests Selenium reproduisent toutes sortes d&#8217;interactions qu&#8217;un utilisateur peut effectuer sur un navigateur web. Un test Selenium est constitué d&#8217;un script pouvant être écrit dans plusieurs langages (Java, Html, C#, Groovy, Php, etc.). Ce script est ensuite interprété par le serveur <a
href="http://seleniumhq.org/projects/remote-control/" title="Selenium RC" >Selenium RC</a>, qui s&#8217;occupe d&#8217;envoyer les bonnes commandes au navigateur. Les avantages de Selenium sont d&#8217;être compatible avec de nombreux navigateurs (Internet Explorer, Firefox, Chrome, etc.) et d&#8217;offrir un outil de création de test, appelé <a
href="http://seleniumhq.org/projects/ide/" title="Selenium IDE" >Selenium IDE</a>. Ce dernier est disponible sous forme d&#8217;un plugin Firefox. Les scripts Selenium sont relativement simples à coder directement, puisqu&#8217;il s&#8217;agit à chaque fois d&#8217;indiquer le type d&#8217;interaction (clic, saisie, etc.), puis de sélectionner l&#8217;élément en jeu (lien, tableau, div, input, etc.), et enfin d&#8217;indiquer une éventuelle valeur. Cependant, on aurait souhaité la disponibilité de Selenium IDE sur d&#8217;autres navigateurs.</p><h3><a
name="VersuneautomatisationdestestsS"></a>Vers une automatisation des tests Selenium avec Maven</h3><p>L&#8217;exécution automatique des tests unitaires sur un serveur d&#8217;intégration continue est une pratique très répandue. En utilisant une base de données en mémoire, comme <a
href="http://hsqldb.org/" title="HSQLDB" >HSQLDB</a>, les tests unitaires peuvent être exécutés facilement quel que soit l&#8217;environnement. De la même manière, il est intéressant de pouvoir lancer automatiquement ces tests fonctionnels sur un serveur d&#8217;intégration. Cependant, la mise en place est plus complexe: à chaque exécution, il faut au préalable s&#8217;assurer que la base de données soit dans un état cohérent (en terme de jeu de données), mais aussi que les serveurs web et Selenium RC soient démarrés.</p><p>Voici les étapes que nous allons automatiser à l&#8217;aide de l&#8217;outil Maven :</p><ol><li>Initialiser la base de données avec un jeu de données : Plugin maven <a
href="http://mojo.codehaus.org/sql-maven-plugin/" title="SQL" >SQL</a>.</li><li>Construire l&#8217;application web et la déployer sur un serveur web Jetty : Plugin maven <a
href="http://docs.codehaus.org/display/JETTY/Maven+Jetty+Plugin" title="Jetty" >Jetty</a>.</li><li>Démarrer le serveur Selenium RC : Plugin maven <a
href="http://mojo.codehaus.org/selenium-maven-plugin/" title="Selenium" >Selenium</a></li><li>Exécuter les tests Selenium : Plugin maven <a
href="http://maven.apache.org/plugins/maven-failsafe-plugin/" title="Failsafe" >Failsafe</a>.</li></ol><p>Comme vous pouvez le constater, cette solution repose sur l&#8217;outil de build Maven et le serveur web <a
href="http://www.mortbay.org/" title="Jetty" >Jetty</a>. Dans un précédent <a
href="http://blog.xebia.fr/2009/09/02/automatiser-ses-tests-fonctionnels-avec-ant/" title="article" >article</a>, j&#8217;avais proposé une autre solution basée sur l&#8217;outil <a
href="http://ant.apache.org/" title="Ant" >Ant</a>. Avant d&#8217;aller plus loin, il est important de connaître le cycle de vie du build de Maven. Voici l&#8217;ordre d&#8217;exécution des principales phases du build :</p><ul><li>validate</li><li>compile</li><li>package</li><li><strong>test</strong></li><li><strong>pre-integration-test</strong></li><li><strong>integration-test</strong></li><li><strong>post-integration-test</strong></li><li><strong>verify</strong></li><li>install</li><li>deploy</li></ul><p>Lorsqu&#8217;on lance une phase, toutes les phases qui la précèdent sont exécutées au préalable. La phase <em>test</em> exécute les tests unitaires. Il existe aussi une phase <em>integration-test</em> qui va nous servir à exécuter les tests Selenium. Cette phase est précédée de la phase <em>pre-integration-test</em> et suivie de la phase <em>post-integration-test</em> qui vont respectivement nous aider à préparer puis nettoyer l&#8217;environnement d&#8217;exécution : démarrage et arrêt des serveurs Web et Selenium RC typiquement. Pour lancer les tests Selenium, il est nécessaire de lancer la commande <code>mvn verify</code> et non <code>mvn integration-test</code>, sinon la phase <em>post-integration-test</em> ne sera pas exécutée.</p><p>Je vais maintenant aborder l&#8217;intégration de chacune des briques (Selenium, base de données, et serveur web) dans Maven.</p><h3><a
name="IntgrationdeSeleniumdansMaven"></a>Intégration de Selenium dans Maven</h3><p>L&#8217;intégration de Selenium dans Maven se décompose en deux étapes :</p><ul><li>Déclarer l&#8217;exécution des tests Selenium via le plugin <a
href="http://maven.apache.org/plugins/maven-failsafe-plugin/" title="Failsafe" >Failsafe</a>.</li><li>Démarrer le serveur Selenium RC en <em>pre-integration-test</em> et l&#8217;arrêter en <em>post-integration-test</em>.</li></ul><h4><a
name="SolutionExcuterlestestsaveclep"></a>Solution 1: Exécuter les tests avec le plugin Surefire</h4><p>Pour intégrer l&#8217;exécution des tests Selenium, il existe une première solution basée sur le plugin <a
href="http://maven.apache.org/plugins/maven-surefire-plugin/" title="Surefire" >Surefire</a>. La fonction première de ce plugin est d&#8217;exécuter les tests unitaires lors de la phase <em>test</em> et de générer un rapport. L&#8217;idée ici est de considérer les tests Selenium, comme des tests unitaires JUnit. Pour inclure les tests Selenium, il faut définir un plan d&#8217;exécution <em>surefire-integration-test</em> associé à la phase <em>integration-test</em> en incluant dedans les classes de test Selenium. Par ailleurs, il faut exclure les tests Selenium de la phase <em>test</em> (dans <em>&lt;configuration&gt;</em>). On obtient alors la déclaration du plugin suivant dans le pom :</p><pre class="brush: xml; title: ; notranslate">
&lt;plugin&gt;
   &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
   &lt;artifactId&gt;maven-surefire-plugin&lt;/artifactId&gt;
   &lt;configuration&gt;
      &lt;includes&gt;
         &lt;!-- Inclure les tests unitaires ici ... --&gt;
      &lt;/includes&gt;
      &lt;excludes&gt;
         &lt;exclude&gt;com/xebia/selenium/**/*Test.java&lt;/exclude&gt;  &lt;!-- Exclure les tests Selenium ici ... --&gt;
      &lt;/excludes&gt;
   &lt;/configuration&gt;
   &lt;executions&gt;
      &lt;execution&gt;
         &lt;id&gt;surefire-integration-test&lt;/id&gt;
         &lt;phase&gt;integration-test&lt;/phase&gt;
         &lt;goals&gt;
            &lt;goal&gt;test&lt;/goal&gt; &lt;!-- La phase integration-test va lancer les tests... --&gt;
         &lt;/goals&gt;
         &lt;configuration&gt;
            &lt;skip&gt;false&lt;/skip&gt;
            &lt;includes&gt;
               &lt;include&gt;com/xebia/selenium/**/*Test.java&lt;/include&gt; &lt;!-- ... Inclure les tests Selenium ici --&gt;
            &lt;/includes&gt;
         &lt;/configuration&gt;
      &lt;/execution&gt;
   &lt;/executions&gt;
&lt;/plugin&gt;
</pre><p>La solution <em>Surefire</em> n&#8217;est cependant pas optimale. En effet, Surefire a été conçu à l&#8217;origine uniquement pour les tests unitaires. Ce qui explique que si un test échoue, le build maven s&#8217;arrêtera à la phase <em>integration-test</em>, sans aller jusqu&#8217;au <em>post-integration-test</em>. Du coup, l&#8217;environnement d&#8217;exécution ne sera pas correctement nettoyé.</p><h4><a
name="SolutionrecommandeExcuterleste"></a>Solution 2 recommandée: Exécuter les tests avec le plugin FailSafe</h4><p>Pour corriger les limitations de Surefire, un nouveau plugin <em>FailSafe</em> dédié aux tests d&#8217;intégration a été créé. Ce plugin est un <a
href="http://fr.wikipedia.org/wiki/Fork_%28d%C3%A9veloppement_logiciel%29" title="fork" >fork</a> de Surefire, qui ne bloque pas le cycle de vie du build en cas d&#8217;échec. En lançant les tests Selenium avec <code>mvn verify</code> et non <code>mvn integration-test</code>, on s&#8217;assure que la phase <em>post-integration-test</em> sera bien exécutée en cas d&#8217;erreurs. Le rôle de la phase <em>verify</em> est donc clairement de vérifier l&#8217;état d&#8217;exécution des tests d&#8217;intégration. Voici comment déclarer le plugin <em>FailSafe</em> dans le pom :</p><pre class="brush: xml; title: ; notranslate">
&lt;plugin&gt;
   &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
   &lt;artifactId&gt;maven-failsafe-plugin&lt;/artifactId&gt;
   &lt;version&gt;2.7.2&lt;/version&gt;
   &lt;configuration&gt;
      &lt;reportsDirectory&gt;${basedir}/target/surefire-reports&lt;/reportsDirectory&gt;
      &lt;includes&gt;
         &lt;include&gt;com/xebia/selenium/**/*.java&lt;/include&gt; &lt;!-- ... inclure les tests Selenium --&gt;
      &lt;/includes&gt;
      &lt;systemPropertyVariables&gt;
         &lt;jetty.port&gt;${jetty.port}&lt;/jetty.port&gt;
         &lt;jetty.context&gt;${artifactId}&lt;/jetty.context&gt;
      &lt;/systemPropertyVariables&gt;
   &lt;/configuration&gt;
   &lt;executions&gt;
      &lt;execution&gt;
         &lt;id&gt;integration-test&lt;/id&gt;
         &lt;goals&gt;
            &lt;goal&gt;integration-test&lt;/goal&gt;
         &lt;/goals&gt;
      &lt;/execution&gt;
      &lt;execution&gt;
         &lt;id&gt;verify&lt;/id&gt;
         &lt;goals&gt;
            &lt;goal&gt;verify&lt;/goal&gt;
         &lt;/goals&gt;
      &lt;/execution&gt;
   &lt;/executions&gt;
&lt;/plugin&gt;
</pre><p>Afin de rendre paramétrable certaines valeurs de contexte dans vos classes de tests Selenium, comme le port et le contexte de l&#8217;application, il est possible d&#8217;indiquer des propriétés systèmes dans le plugin <em>FailSafe</em>. Pour cela, il suffit d&#8217;ajouter l&#8217;élément <em>&lt;systemPropertyVariables&gt;</em> dans <em>&lt;configuration&gt;</em> avec les différentes propriétés.<br
/> On remarque aussi la propriété <em>&lt;reportsDirectory&gt;</em> qui permet de générer les rapports de tests dans le même dossier que Surefire. Le serveur d&#8217;intégration inclura ainsi les résultats des tests Selenium avec ceux des tests unitaires. Une autre solution est de définir l&#8217;emplacement des rapports directement sur le serveur d&#8217;intégration continue. Par défaut, FailSafe place les rapports dans <em>basedir/target/failsafe-reports</em>.</p><h4><a
name="AjoutdupluginSeleniumPourdmarr"></a>Ajout du plugin Selenium (Pour démarrer et arrêter le serveur Selenium RC)</h4><p>Afin de lancer automatiquement le serveur Selenium RC pendant l&#8217;exécution de la phase <strong>integration-test</strong>, il faut ajouter <a
href="http://mojo.codehaus.org/selenium-maven-plugin/" title="le plugin" >le plugin</a> suivant dans le pom :</p><pre class="brush: xml; title: ; notranslate">
&lt;plugin&gt;
   &lt;groupId&gt;org.codehaus.mojo&lt;/groupId&gt;
   &lt;artifactId&gt;selenium-maven-plugin&lt;/artifactId&gt;
   &lt;version&gt;1.0.1&lt;/version&gt;
   &lt;executions&gt;
      &lt;execution&gt;
         &lt;id&gt;start&lt;/id&gt;
         &lt;phase&gt;pre-integration-test&lt;/phase&gt;
         &lt;goals&gt;
            &lt;goal&gt;start-server&lt;/goal&gt;
         &lt;/goals&gt;
         &lt;configuration&gt;
            &lt;background&gt;true&lt;/background&gt;
         &lt;/configuration&gt;
      &lt;/execution&gt;
      &lt;execution&gt;
         &lt;id&gt;stop&lt;/id&gt;
         &lt;phase&gt;post-integration-test&lt;/phase&gt;
         &lt;goals&gt;
            &lt;goal&gt;stop-server&lt;/goal&gt;
         &lt;/goals&gt;
      &lt;/execution&gt;
   &lt;/executions&gt;
&lt;/plugin&gt;
</pre><p>L&#8217;option <em>&lt;background&gt;true&lt;/background&gt;</em> permet de ne pas bloquer le processus Maven pendant que le serveur Selenium RC est démarré.</p><h3><a
name="Configurationduserveurwebavecl"></a>Configuration du serveur web avec le plugin Jetty</h3><p>Préalablement à l&#8217;exécution des tests Selenium, il est nécessaire de démarrer l&#8217;application sur un serveur web durant la phase <em>pre-integration-test</em>. Le serveur sera arrêté durant la phase <em>post-integration-test</em>. Voici la manière de déclarer <a
href="http://docs.codehaus.org/display/JETTY/Maven+Jetty+Plugin" title="le plugin Jetty" >le plugin Jetty</a> dans le pom :</p><pre class="brush: xml; title: ; notranslate">
&lt;plugin&gt;
   &lt;groupId&gt;org.mortbay.jetty&lt;/groupId&gt;
   &lt;artifactId&gt;maven-jetty-plugin&lt;/artifactId&gt;
   &lt;version&gt;6.1.10&lt;/version&gt;
   &lt;configuration&gt;
      &lt;scanIntervalSeconds&gt;10&lt;/scanIntervalSeconds&gt;
      &lt;stopKey&gt;foo&lt;/stopKey&gt;
      &lt;stopPort&gt;9999&lt;/stopPort&gt;
      &lt;webAppSourceDirectory&gt;${webapp.dir}&lt;/webAppSourceDirectory&gt;
      &lt;connectors&gt;
         &lt;connector implementation=&quot;org.mortbay.jetty.nio.SelectChannelConnector&quot;&gt;
            &lt;port&gt;${jetty.port}&lt;/port&gt;
            &lt;maxIdleTime&gt;60000&lt;/maxIdleTime&gt;
         &lt;/connector&gt;
      &lt;/connectors&gt;
   &lt;/configuration&gt;
   &lt;executions&gt;
      &lt;execution&gt;
         &lt;id&gt;start-jetty&lt;/id&gt;
         &lt;phase&gt;pre-integration-test&lt;/phase&gt;
         &lt;goals&gt;
            &lt;goal&gt;run&lt;/goal&gt;
         &lt;/goals&gt;
         &lt;configuration&gt;
            &lt;scanIntervalSeconds&gt;0&lt;/scanIntervalSeconds&gt;
            &lt;daemon&gt;true&lt;/daemon&gt;
         &lt;/configuration&gt;
      &lt;/execution&gt;
      &lt;execution&gt;
         &lt;id&gt;stop-jetty&lt;/id&gt;
         &lt;phase&gt;post-integration-test&lt;/phase&gt;
         &lt;goals&gt;
            &lt;goal&gt;stop&lt;/goal&gt;
         &lt;/goals&gt;
      &lt;/execution&gt;
   &lt;/executions&gt;
&lt;/plugin&gt;
</pre><p>L&#8217;attribut <em>&lt;webAppSourceDirectory&gt;</em> permet d&#8217;indiquer le chemin de l&#8217;application web. On peut aussi remarquer l&#8217;utilisation d&#8217;une propriété <em>jetty.port</em> pour mettre en paramètre le port d&#8217;écoute de Jetty. Celle-ci se déclare de la manière suivante dans le pom.xml :</p><pre class="brush: xml; title: ; notranslate">
&lt;properties&gt;
   &lt;jetty.port&gt;9090&lt;/jetty.port&gt;
   ......
&lt;/properties&gt;
</pre><h3><a
name="Manipulationdelabasededonnes"></a>Manipulation de la base de données</h3><p>Il existe de nombreuses solutions pour préparer la base de données aux tests d&#8217;intégration. Même s&#8217;il est possible d&#8217;utiliser une base de données en mémoire, comme <a
href="http://hsqldb.org/" title="HSQLDB" >HSQLDB</a>, je recommanderais plutôt d&#8217;utiliser le même type de base qu&#8217;en production, parce que nous exécutons des tests end-to-end. Pour insérer les jeux de données, il est possible d&#8217;utiliser deux plugins Maven : SQL ou DbUnit. J&#8217;ai privilégié l&#8217;utilisation du <a
href="http://mojo.codehaus.org/sql-maven-plugin/" title="plugin SQL" >plugin SQL</a>, car mes scripts étaient déjà écrits en SQL. En effet, <a
href="http://www.dbunit.org/" title="DbUnit" >DbUnit</a> utilise son propre format en XML.<br
/> Le plugin SQL permet aussi d&#8217;exécuter des procédures stockées et des fonctions en changeant le type de délimiteur, qui est par défaut &#8216;;&#8217;. Dans <em>&lt;configuration&gt;</em>, on définit le driver, ainsi que les paramètres de connexion. J&#8217;ai ajouté une dépendance au driver d&#8217;Oracle dans mon exemple. Le paramètre <em>&lt;autocommit&gt;</em> permet d&#8217;indiquer si l&#8217;on veut que les scripts soient exécutés de manière transactionnelle ou non.<br
/> Ensuite, il suffit de déclarer un plan d&#8217;exécution <em>&#8216;insert_data&#8217;</em> pour la phase <em>pre-integration-test</em> et un autre <em>&#8216;drop_data&#8217;</em> pour la phase <em>post-integration-test</em> en indiquant les scripts SQL.</p><pre class="brush: xml; title: ; notranslate">
&lt;plugin&gt;
   &lt;groupId&gt;org.codehaus.mojo&lt;/groupId&gt;
   &lt;artifactId&gt;sql-maven-plugin&lt;/artifactId&gt;
   &lt;version&gt;1.4&lt;/version&gt;
   &lt;dependencies&gt;
      &lt;dependency&gt;
         &lt;groupId&gt;com.oracle&lt;/groupId&gt;
         &lt;artifactId&gt;ojdbc14&lt;/artifactId&gt;
         &lt;version&gt;10.2.0.4&lt;/version&gt;
      &lt;/dependency&gt;
   &lt;/dependencies&gt;
   &lt;configuration&gt;
      &lt;driver&gt;oracle.jdbc.driver.OracleDriver&lt;/driver&gt;
      &lt;url&gt;jdbc:oracle:thin:@localhost:1521:capitaineHadoc&lt;/url&gt;
      &lt;username&gt;tintin&lt;/username&gt;
      &lt;password&gt;milou&lt;/password&gt;
      &lt;delimiter&gt;;&lt;/delimiter&gt;
      &lt;delimiterType&gt;normal&lt;/delimiterType&gt;
      &lt;keepFormat&gt;true&lt;/keepFormat&gt;
      &lt;skip&gt;${maven.test.skip}&lt;/skip&gt;
      &lt;autocommit&gt;true&lt;/autocommit&gt;
   &lt;/configuration&gt;
   &lt;executions&gt;
      &lt;execution&gt;
         &lt;id&gt;insert-data&lt;/id&gt;
         &lt;phase&gt;pre-integration-test&lt;/phase&gt;
         &lt;goals&gt;
            &lt;goal&gt;execute&lt;/goal&gt;
         &lt;/goals&gt;
         &lt;configuration&gt;
            &lt;orderFile&gt;ascending&lt;/orderFile&gt;
            &lt;fileset&gt;
               &lt;basedir&gt;${basedir}/path/to/sql/files&lt;/basedir&gt;
               &lt;includes&gt;
                  &lt;include&gt;insert-data.sql&lt;/include&gt;
               &lt;/includes&gt;
            &lt;/fileset&gt;
         &lt;/configuration&gt;
      &lt;/execution&gt;
      &lt;execution&gt;
         &lt;id&gt;drop-data&lt;/id&gt;
         &lt;phase&gt;post-integration-test&lt;/phase&gt;
         &lt;goals&gt;
            &lt;goal&gt;execute&lt;/goal&gt;
         &lt;/goals&gt;
         &lt;configuration&gt;
            &lt;orderFile&gt;ascending&lt;/orderFile&gt;
            &lt;fileset&gt;
               &lt;basedir&gt;${basedir}/path/to/sql/files&lt;/basedir&gt;
               &lt;includes&gt;
                  &lt;include&gt;drop-data.sql&lt;/include&gt;
               &lt;/includes&gt;
            &lt;/fileset&gt;
         &lt;/configuration&gt;
      &lt;/execution&gt;
   &lt;/executions&gt;
&lt;/plugin&gt;
</pre><h3><a
name="ExecutiondestestsSelenium"></a>Execution des tests Selenium</h3><h4><a
name="DepuisMaven"></a>Depuis Maven</h4><p>Après avoir déclaré tous les plugins dans le <em>pom.xml</em>, il suffit de lancer la commande suivante :</p><pre class="brush: bash; title: ; notranslate">
mvn verify
</pre><p>Voici alors une synthèse des logs que l&#8217;ont obtient :</p><pre class="brush: bash; title: ; notranslate">
[INFO] [selenium:start-server {execution: start}]
[INFO] 1
[INFO] [jetty:run {execution: start-jetty}]
[INFO] [failsafe:integration-test {execution: integration-test}]
[INFO] [selenium:stop-server {execution: stop}]
[INFO] 1
[INFO] [jetty:stop {execution: stop-jetty}]
[INFO] [failsafe:verify {execution: verify}]
[INFO] Failsafe report directory: D:Javasoftworkspacepid-trunktargetsurefire-reports
</pre><h4><a
name="DepuisvotreIDE"></a>Depuis votre IDE</h4><p>Lorsqu&#8217;on développe de nouveaux tests Selenium ou tout simplement pour débugger, il est nécessaire de les exécuter sur son IDE. Pour parvenir à cela, il suffit de lancer les commandes suivantes (dans des terminaux différents) :</p><ol><li>Préparer la base de données si nécessaire :<pre class="brush: bash; title: ; notranslate">mvn sql:execute</pre></li><li>Démarrer le serveur <em>Selenium RC </em>:<pre class="brush: bash; title: ; notranslate">mvn selenium:start-server</pre></li><li>Démarrer le serveur web <em>Jetty </em>:<pre class="brush: bash; title: ; notranslate">mvn jetty:run</pre></li><li>Exécuter le test depuis votre IDE.</li></ol><h3><a
name="Conclusion"></a>Conclusion</h3><p>Avec ses nombreux plugins, Maven est un puissant outil pour automatiser l&#8217;exécution des tests Selenium. Les solutions proposées dans cet article peuvent servir de manière générale aux tests d&#8217;intégration. Les prochaines versions de Maven ne prévoient pas une séparation des sources entre les tests unitaires et les tests d&#8217;integration (<em>src/main/test</em> vs <em>src/main/it</em>). En effet, le plugin <em>FailSafe</em> permet de gérer les tests d&#8217;intégration de manière dédiée.</p><div
class="shr-publisher-7049"></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div><div
class='shareaholic-like-buttonset' style='float:none;height:30px;'><a
class='shareaholic-googleplusone' data-shr_size='medium' data-shr_count='true' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2011%2F02%2F18%2Fautomatiser-les-tests-selenium-avec-maven%2F' data-shr_title='Automatiser+les+tests+Selenium+avec+Maven'></a><a
class='shareaholic-tweetbutton' data-shr_count='horizontal' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2011%2F02%2F18%2Fautomatiser-les-tests-selenium-avec-maven%2F' data-shr_title='Automatiser+les+tests+Selenium+avec+Maven'></a></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div>]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2011/02/18/automatiser-les-tests-selenium-avec-maven/feed/</wfw:commentRss> <slash:comments>4</slash:comments> </item> <item><title>NoThunes, tests en tout genre et qualité de code</title><link>http://blog.xebia.fr/2010/12/09/nothunes-tests-en-tout-genre-et-qualite-de-code/</link> <comments>http://blog.xebia.fr/2010/12/09/nothunes-tests-en-tout-genre-et-qualite-de-code/#comments</comments> <pubDate>Thu, 09 Dec 2010 12:03:45 +0000</pubDate> <dc:creator>Aurélien Maury</dc:creator> <category><![CDATA[Divers]]></category> <category><![CDATA[Tests]]></category> <category><![CDATA[Grails]]></category> <category><![CDATA[Groovy]]></category> <category><![CDATA[NoThunes]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=6098</guid> <description><![CDATA[Avec un peu de retard sur les autres, voici le cinquième article de cette série sur Grails. Nous allons parler ici de : Tests unitaires Tests d&#8217;intégration Mesures de qualité de code Intégration continue En appliquant le tout à notre projet test bien aimé : NoThunes. Tout code sera testé, sinon par vous, alors par [...]]]></description> <content:encoded><![CDATA[<p><img
src="http://blog.xebia.fr/wp-content/uploads/2010/09/nothunes_mini_logo.png" style="margin: 1em 1em 1em 1em; float: right;" /></p><p>Avec un peu de retard sur les autres, voici le cinquième article de cette <a
href="http://blog.xebia.fr/tag/nothunes/">série sur Grails</a>. Nous allons parler ici de :</p><ul><li>Tests unitaires</li><li>Tests d&#8217;intégration</li><li>Mesures de qualité de code</li><li>Intégration continue</li></ul><p>En appliquant le tout à notre projet test bien aimé : NoThunes.</p><h3><a
name="Toutcodeseratestsinonparvousal"></a>Tout code sera testé, sinon par vous, alors par vos utilisateurs &#8230;</h3><p>La nécessité de tester notre code, quel qu&#8217;il soit, a été largement décrite, soutenue et argumentée. Par conséquent, nous ne reviendrons pas dessus dans cet article. Je tiens à m&#8217;excuser pour avoir placé en dernier lieu l&#8217;activité qui devrait ouvrir le bal dans la rédaction d&#8217;une application (le TDD c&#8217;est bon, mangez-en). Cela m&#8217;a paru plus attractif de mettre les mains dans le cambouis de suite. Mais maintenant nous allons passer aux choses sérieuses : les Tests.</p><p>Scott Davis, sur le blog d&#8217;IBM, a rédigé une série d&#8217;articles <a
href="http://www.ibm.com/developerworks/views/java/libraryview.jsp?search_by=mastering+grails" title="Mastering Grails" >Mastering Grails</a> dont 2 nous concernent plus particulièrement ici :</p><ul><li><a
href="http://www.ibm.com/developerworks/java/library/j-grails10209/index.html" title="Mock Testing with Grails" >Mock Testing with Grails</a></li><li><a
href="http://www.ibm.com/developerworks/java/library/j-grails10148/index.html" title="Testing your Grails application" >Testing your Grails application</a></li></ul><p>Les tests à la mode Grails sont largement bien décrits dans ces 2 articles. Je vais donc vous fournir ici mon retour d&#8217;expérience sur l&#8217;application en conditions réelles de ces conseils. Les tests unitaires au sens propre sont très bien décrits dans ces 2 articles, nous allons donc directement sauter aux tests d&#8217;intégration.</p><h3><a
name="TestsdintgrationGrailsdanslapr"></a>Tests d&#8217;intégration Grails dans la pratique</h3><p>Durant la phase des tests d&#8217;intégration, l&#8217;application est chargée entièrement, dans un <a
href="http://www.grails.org/Environments">mode test</a>. C&#8217;est l&#8217;équivalent du lancement de <code>grails test run-app</code>. Par conséquent nous disposons d&#8217;une base de données et de tous nos composants.</p><p>D&#8217;après Burt Beckwith, membre de la team Grails, il ne faut pas utiliser le framework de mock de Grails pour tester les classes de domaine. Sinon, tout ce qui est testé, c&#8217;est le framework de mock en lui-même et il ne supporte pas toutes les fonctionnalités de GORM (comme les closures <em>withCriteria</em> par exemple !). Pour les classes de domaine, il faut donc utiliser uniquement des tests d&#8217;intégration. L&#8217;exception à cette règle concerne les tests de méthode n&#8217;impliquant ni GORM, ni la base de données, comme les tests de validation de contraintes (regarder du côté de <code><a
href="http://www.ibm.com/developerworks/java/library/j-grails10209/index.html#N10205" title="mockForConstraintsTests()" >mockForConstraintsTests()</a></code> pour ça).</p><p>Cette méthode est un peu radicale et suppose que, dès qu&#8217;on fait usage, à un quelconque endroit, de requêtes nommées ou de closures Criteria, on se retrouve obligé de ne coder <em>que</em> des tests d&#8217;intégration. Une alternative consiste à <a
href="http://adhockery.blogspot.com/2010/01/using-gmock-to-complement-grails.html" title="utiliser GMock" >utiliser GMock</a> pour combler les manques actuels de Grails.</p><h4><a
name="Jeuxdedonnes"></a>Jeux de données</h4><p>Lorsqu&#8217;on code des tests d&#8217;intégration il est important de connaitre le contenu de la base de données avant chaque test. Il est donc utile de pouvoir se baser sur des jeux de données pour initialiser la base. Parmi les nombreuses solutions qui s&#8217;offrent à nous, voici celles qui ont retenu mon attention :</p><ul><li><strong>Utiliser la classe de configuration</strong> <code>Bootstrap.groovy</code> : Cela suppose de charger l&#8217;intégralité des données utiles pour tous les tests d&#8217;intégration, ce qui peut vite devenir encombrant. De plus le code nécessaire à instancier, lier et sauver toutes les instances utiles est assez verbeux.</li><li><strong>Le plugin</strong> <strong><a
href="http://www.grails.org/plugin/dbunit-operator" title="DbUnit Operator" >DbUnit Operator</a></strong> : Permet d&#8217;injecter à la demande des jeux de données écrits en XML. Ce plugin est basé sur la <a
href="http://www.dbunit.org/" title="librairie" >librairie</a> du même nom. C&#8217;est robuste et souple à la fois. L&#8217;inconvénient est que le modèle XML est centré sur le schéma de la base et va nécessiter une maintenance non négligeable.</li><li><strong>Le couple de plugin</strong> <strong><a
href="http://gpc.github.com/grails-fixtures/" title="Fixtures" >Fixtures</a>/<a
href="http://www.grails.org/plugin/build-test-data" title="BuildTestData" >BuildTestData</a></strong> : Fixtures permet de créer des jeux de données et de les sauvegarder en base rapidement avec une syntaxe concise. C&#8217;est la solution qui m&#8217;a semblée la plus pratique. L&#8217;association avec le plugin BuildTestData autorise à raccourcir encore la définition d&#8217;entités. Les champs non remplis manuellement dans une fixture peuvent être remplis automatiquement par BuildTestData.</li></ul><p>Après expérimentation, la dernière solution s&#8217;est révélée la plus simple et efficace. La documentation du plugin est un peu succincte et ne décrit pas de façon <em>explicite</em> tous les cas d&#8217;utilisation. En exclusivité, je vais prendre comme exemple un cas non documenté, sous vos yeux ébahis : l&#8217;utilisation conjointe des 2 plugins dans une fixture externe. Commençons par l&#8217;installation :</p><pre class="brush: java; title: ; notranslate">
grails install-plugin fixtures
grails install-plugin build-test-data
</pre><p>L&#8217;installation du plugin Fixtures entraine l&#8217;apparition d&#8217;un répertoire <code>fixtures/</code> à la racine du projet. Tous les fichiers de fixtures déposés dedans seront rendus accessible pour être chargé dans le code de nos tests. Il est possible de mettre directement les fixtures dans notre code (comme décrit <a
href="http://gpc.github.com/grails-fixtures/docs/manual/guide/2.%20Defining%20The%20Data.html" title="dans la documentation" >dans la documentation</a>) mais j&#8217;ai préféré l&#8217;option de chargement de fichiers externes pour éviter d&#8217;introduire du bruit dans le code. Voici une fixture appliquée à notre modèle :</p><p><code><strong>fixtures/initialDev.groovy</strong></code></p><pre class="brush: java; title: ; notranslate">
import fr.xebia.nothunes.domain.*
import fr.xebia.nothunes.security.*
def member = User.findByUsername('user')
member.confirmPasswd = member.passwd
build {
  rtnp(Band) {
    name = 'RTNP'
    webSite = 'http://rtnp.org'
    owner = member
  }
  promotionCanape(Album) {
    name = &quot;Promotion Canapé&quot;
    band = rtnp
  }
  zombieNow(Track) {
    name = &quot;Zombie Now&quot;
    album = promotionCanape
  }
}
</pre><p>Décryptons ce fichier. Nous commençons par récupérer une instance de compte utilisateur en base, pour lui attribuer un <code>Band</code>. Cet utilisateur a été inséré dans la base via la classe de configuration <code>Bootstrap</code>, on peut donc compter dessus. La closures <code>build</code> sera interprétée par Fixtures pour créer les objets et les sauver en base. Fixture permet d&#8217;utiliser une closure <em>build</em>, qui s&#8217;appuie sur le plugin <code>BuildTestsData</code>, qui remplira automatiquement les champs non definis dans la fixture, de façon à ce que l&#8217;instance créée respecte ses contraintes d&#8217;intégrité.</p><p>Il est important de noter que les liens tissés entre les objets de la fixture sont écrits de façon directe. Par exemple la propriété <code>band</code> de notre instance <code>promotionCanape</code> est directement le nom de l&#8217;instance décrite juste au dessus. Pas besoin de sauvegarde intermédiaire, ce qui fait gagner pas mal de temps d&#8217;écriture.</p><p>Maintenant que nous disposons d&#8217;un petit jeu de données, pour le charger dans un test, il suffit de faire comme ceci :</p><pre class="brush: java; title: ; notranslate">
package fr.xebia.nothunes.domain
import grails.test.*
import fr.xebia.nothunes.domain.*
import fr.xebia.nothunes.security.*
class TrackIntegTests extends GrailsUnitTestCase {
  // definition de l'attribut fixtureLoader pour injection automatique
  def fixtureLoader
  protected void setUp() {
    super.setUp()
  }
  protected void tearDown() {
    super.tearDown()
  }
  void testLoadFixture() {
    // chargement du fichier fixtures/initialDev.groovy
    def fixture = fixtureLoader.load('initialDev')
    def result = Track.listOrderByName()
    assertEquals 1, result.size()
    assertEquals 'Zombie Now', result[0].name
  }
  void testNotLoadedFixture() {
    def result = Track.listOrderByName()
    assertEquals 0, result.size()
  }
}
</pre><p>Comme on peut le voir, la durée de vie des données en base ne dépasse pas la méthode de test. Ce comportement est plutôt pratique pour éviter que nos tests d&#8217;intégration se polluent les uns les autres.</p><h3><a
name="Testfonctionnels"></a>Test fonctionnels</h3><p>Pour aller encore un peu plus loin dans les tests, il faut monter encore d&#8217;un cran et passer aux tests d&#8217;IHM. Une référence dans le monde du web en Java : Selenium. C&#8217;est le premier nom qui m&#8217;est venu et j&#8217;ai donc cherché un plugin Grails. Il existe bel et bien mais la documentation initiale m&#8217;a fait faire demi-tour. Les tests peuvent être rédigés en trois formats différents, je vous colle des exemple de chacun pour que vous puissiez juger :</p><ul><li>HTML :</li></ul><pre class="brush: java; title: ; notranslate">
&lt;html&gt;
&lt;head&gt;
&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot;&gt;
&lt;title&gt;test1&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;table cellpadding=&quot;1&quot; cellspacing=&quot;1&quot; border=&quot;1&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;td colspan=&quot;3&quot;&gt;test1&lt;/td&gt;&lt;/tr&gt;
&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;open&lt;/td&gt;&lt;td&gt;/selenium-test/&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;clickAndWait&lt;/td&gt;&lt;td&gt;link=BookController&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;clickAndWait&lt;/td&gt;&lt;td&gt;link=New Book&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;type&lt;/td&gt;&lt;td&gt;title&lt;/td&gt;&lt;td&gt;book1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;clickAndWait&lt;/td&gt; &lt;td&gt;//input[@value='Create']&lt;/td&gt; &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;clickAndWait&lt;/td&gt; &lt;td&gt;link=Book List&lt;/td&gt; &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;verifyTextPresent&lt;/td&gt; &lt;td&gt;book1&lt;/td&gt; &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre><ul><li>Pipe separated values :</li></ul><pre class="brush: java; title: ; notranslate">
open|/selenium-test
clickAndWait|link=BookController
clickAndWait|link=New Book
type|title|book2
clickAndWait|//input[@value='Create']
clickAndWait|link=Book List
verifyTextPresent|book2
</pre><ul><li>Groovy Server Pages :</li></ul><pre class="brush: java; title: ; notranslate">
&lt;g:set var=&quot;bookTitle&quot; value=&quot;book0d&quot;/&gt;
&lt;sel:test name=&quot;MyTest&quot;&gt;
&lt;sel:row command=&quot;open&quot; target=&quot;${request.contextPath}&quot;/&gt;
&lt;sel:row line=&quot;clickAndWait|link=BookController&quot;/&gt;
&lt;sel:row line=&quot;clickAndWait|link=New Book&quot;/&gt;
&lt;sel:row command=&quot;type&quot; target=&quot;title&quot; value=&quot;${bookTitle}&quot;/&gt;
&lt;sel:row line=&quot;clickAndWait|//input[@value='Create']&quot;/&gt;
&lt;sel:row line=&quot;clickAndWait|link=Book List&quot;/&gt;
&lt;sel:row line=&quot;verifyTextPresent|${bookTitle}&quot;/&gt;
&lt;/sel:test&gt;
</pre><p>Si l&#8217;une de ces syntaxes vous emballe, faites en bon usage. Personnellement j&#8217;ai été refroidi par l&#8217;aspect très compact et plutôt dur à relire. J&#8217;ai donc continué mes investigations, et suis finalement tombé sur le plugin <a
href="http://www.grails.org/plugin/webtest" title="WebTest" >WebTest</a>, basé sur la librairie de <a
href="http://webtest.canoo.com/webtest/manual/WebTestHome.html" title="Canoo" >Canoo</a>. Une librairie beaucoup plus orienté Groovy que ne l&#8217;est le plugin Selenium. De plus WebTest fourni également de jolis rapports HTML plein de couleurs attrayantes, faciles à interpréter. Voici un exemple de ce que peut donner le code d&#8217;un test simpliste avec cette librairie :</p><pre class="brush: java; title: ; notranslate">
package nothunes
import nothunes.common.WebTestsNoThunes
class BandWebTests extends WebTestsNoThunes {
  def fixtureLoader
  void testLogin() {
    invoke(url:'login')
    verifyText(text:'Please Login..')
    def fixture = fixtureLoader.load('initialDev')
    invoke(url:'/navigation')
    verifyText(text:'RTNP')
  }
}
</pre><p>Les actions sont basiques, l&#8217;API est nativement en Groovy, et pour ceux qui ont déjà utilisé Selenium sur des projets Java, ce n&#8217;est vraiment pas dépaysant. La facilité de prise en main et les rapports parlants en font ma préférence pour l&#8217;automatisation de tests d&#8217;IHM web.</p><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/12/webtests-report.png" alt="webtests-report" title="webtests-report" /></div><h3><a
name="Analysestatiqueducode"></a>Analyse statique du code</h3><p>Outre la pose d&#8217;un filet de sécurité autour des fonctionnalités de l&#8217;application, il est également intéressant d&#8217;affiner la qualité du code source produit. L&#8217;analyse statique de code est une méthode rodée chez les développeurs Java pour permettre de trouver des duplications de code, des bugs éventuels, évaluer la complexité, etc. Du coté du langage Groovy, l&#8217;outillage est un peu moins riche mais on trouve quand même quelques petites choses intéressantes.</p><h4><a
name="CodeNarc"></a>CodeNarc</h4><p>Tout d&#8217;abord : <a
href="http://codenarc.sourceforge.net/" title="CodeNarc" >CodeNarc</a>. Ce programme permet de valider du code Groovy en regard de règles de programmation établie. Un jeu de règle est proposé par défaut mais il est possible de définir ses propres règles. Un packaging sous forme de <a
href="http://www.grails.org/plugin/codenarc" title="plugin Grails" >plugin Grails</a> existe également, pour permettre une installation et une utilisation simplifiées :</p><pre class="brush: java; title: ; notranslate">
grails install-plugin codenarc
grails codenarc
</pre><p>Cela va lancer l&#8217;analyse et générer un rapport détaillé de toutes les violations de règles, au format HTML :</p><p><strong>Extrait de CodeNarcReport.html</strong></p><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/12/codenarc_nothunes.png" alt="codenarc_nothunes" title="codenarc_nothunes" width="700px" /></div><p>La présentation est spartiate, mais ça tourne ! Le rapport commence par une vision macro avec la consolidation des erreurs par package, puis viens la liste détaillée de chacun des packages concernés, et enfin la description de chaque type de violation rencontré.<br
/> On peut également <a
href="http://codenarc.sourceforge.net/codenarc-creating-ruleset.html" title="crire soit mme des rgles" >écrire soit même des règles</a> de programmation à respecter. Il est donc aisé de se baser sur cet outil pour faire respecter les normes de votre choix. Pour ceux d&#8217;entre vous qui l&#8217;utilise déjà, sachez que la dernière version (la 0.11) est sortie le 13 novembre avec notamment 50 règles supplémentaires.</p><h4><a
name="GMetrics"></a>GMetrics</h4><p>GMetrics, quant à lui, est centré sur le calcul et la présentation de métriques. Le projet assure le calcul :</p><ul><li>de complexité cyclomatique</li><li>du nombre de lignes par méthode</li><li>du nombre de lignes par classe</li></ul><p>Selon le même principe que CodeNarc, l&#8217;installation de cette librairie se fait comme un plugin au projet : il se lance simplement et génère un rapport HTML :</p><pre class="brush: java; title: ; notranslate">
grails install-plugin gmetrics
grails gmetrics
</pre><p><strong>Extrait d&#8217;un rapport GMetrics</strong></p><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/12/gmetrics_nothunes.png" alt="gmetrics_nothunes" title="gmetrics_nothunes" width="700px"/></div><p>GMetrics est encore assez léger dans les métriques collectées, mais il offre une excellente base de développement pour collecter ses propres métriques. Si par hasard vous en écrivez pour un projet, n&#8217;hésitez pas à en faire profiter la communauté.</p><h3><a
name="Conclusions"></a>Conclusions</h3><p>Il y aurait de quoi écrire un livre entier sur le seul sujet des tests dans le cadre de Grails. Comme d&#8217;habitude, les affinités entre le langage Groovy et le Java font que beaucoup d&#8217;outils Java sont déjà <em>pluginizé</em> ou ne coûterai pas cher à l&#8217;être. Longtemps cantonné aux scripting Java, le langage Groovy trouve ses lettre de noblesse avec Grails. Il est agréable de voir se développer pour le langage Groovy des outils de qualité qui apportent tant au langage Java. En quelques recherches, un projet peut se doter de tout ce qu&#8217;il faut pour développer des tests unitaires, d&#8217;intégration, fonctionnels mais aussi tests d&#8217;acceptance (avec <a
href="http://www.grails.org/plugin/fitnesse" title="Fitnesse" >Fitnesse</a> ou <a
href="http://www.grails.org/plugin/easyb" title="EasyB" >EasyB</a>), ou même des tests de vos fonctions Javascript avec <a
href="http://www.grails.org/plugin/jsunit" title="JsUnit" >JsUnit</a>.</p><p>Dans le framework Grails, les tests sont disponibles pour tous les goûts, toutes les tailles. Le véritable défi consiste à séparer le bon grain de l&#8217;ivraie au milieu du foisonnement de plugins disponibles (sans compter qu&#8217;il est toujours possible d&#8217;intégrer soit même des librairies Java). J&#8217;espère vous avoir apporté, sinon <em>La</em> réponse, au moins l&#8217;éclairage de mon expérience personnelle sur le sujet. Maintenant il ne reste plus qu&#8217;à trouver votre propre style de tests Grails.</p><p><strong>Ressources :</strong></p><ul><li><a
href="http://www.grails.org/Unit+Testing" title="Tests unitaires sur le site Grails" >Tests unitaires sur le site Grails</a></li><li><a
href="http://grails.org/doc/latest/guide/9.%20Testing.html" title="Section Testing de la documentation officielle" >Section Testing de la documentation officielle</a></li><li><a
href="http://www.ibm.com/developerworks/java/library/j-grails10209/index.html" title="Mock Testing with Grails" >Mock Testing with Grails</a></li><li><a
href="http://www.ibm.com/developerworks/java/library/j-grails10148/index.html" title="Testing your Grails application" >Testing your Grails application</a></li><li><a
href="https://github.com/aurelienmaury/nothunes" title="NoThunes sur GitHub" >NoThunes sur GitHub</a></li></ul><div
class="shr-publisher-6098"></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div><div
class='shareaholic-like-buttonset' style='float:none;height:30px;'><a
class='shareaholic-googleplusone' data-shr_size='medium' data-shr_count='true' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2010%2F12%2F09%2Fnothunes-tests-en-tout-genre-et-qualite-de-code%2F' data-shr_title='NoThunes%2C+tests+en+tout+genre+et+qualit%C3%A9+de+code'></a><a
class='shareaholic-tweetbutton' data-shr_count='horizontal' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2010%2F12%2F09%2Fnothunes-tests-en-tout-genre-et-qualite-de-code%2F' data-shr_title='NoThunes%2C+tests+en+tout+genre+et+qualit%C3%A9+de+code'></a></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div>]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2010/12/09/nothunes-tests-en-tout-genre-et-qualite-de-code/feed/</wfw:commentRss> <slash:comments>1</slash:comments> </item> <item><title>TDD et productivité</title><link>http://blog.xebia.fr/2010/11/03/tdd-et-productivite/</link> <comments>http://blog.xebia.fr/2010/11/03/tdd-et-productivite/#comments</comments> <pubDate>Wed, 03 Nov 2010 06:36:57 +0000</pubDate> <dc:creator>Simon Caplette</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[Méthodes agiles]]></category> <category><![CDATA[Tests]]></category> <category><![CDATA[continuous unit testing]]></category> <category><![CDATA[Eclipse]]></category> <category><![CDATA[junit]]></category> <category><![CDATA[TDD]]></category> <category><![CDATA[Test-Driven-Development]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=5762</guid> <description><![CDATA[Quand on est en phase de développement et que l&#8217;on pratique le TDD, il est nécessaire de connaître certaines méthodes pour éviter la répétition, une fatigue des doigts évidente et ainsi améliorer son rendement. Nous essayerons dans cet article de montrer l&#8217;ergonomie d&#8217;Eclipse et l&#8217;utilisation du clavier pour rendre plus courte et plus agréable une [...]]]></description> <content:encoded><![CDATA[<p>Quand on est en phase de développement et que l&#8217;on pratique le TDD, il est nécessaire de connaître certaines méthodes pour éviter la répétition, une fatigue des doigts évidente et ainsi améliorer son rendement.</p><p>Nous essayerons dans cet article de montrer l&#8217;ergonomie d&#8217;Eclipse et l&#8217;utilisation du clavier pour rendre plus courte et plus agréable une session TDD. Nous présenterons quelques plugins Eclipse et aussi un outil souvent négligé que sont les templates de codes.</p><p>Un pattern sera introduit pour faciliter l&#8217;utilisation des objet métiers et POJO dans les tests.</p><p>Enfin, nous présenterons le concept important du <em>Continuous Unit Testing</em> appliqué à l&#8217;IDE et indiquerons et comparerons les plugins Eclipse pour le mettre en œuvre.</p><h3><a
name="TDDetEclipse"></a>TDD et Eclipse</h3><h4><a
name="Raccourcisclaviersutiles"></a>Raccourcis claviers utiles</h4><p>La programmation sur ordinateur est née avant l&#8217;outil qu&#8217;est la souris pour PC. Tout développeur sait que privilégier le clavier à la souris aide à la concentration et apporte un gain de temps énorme.</p><p>Dans cet article, le but n&#8217;est pas d&#8217;avoir une liste exhaustive de raccourcis claviers utiles à la programmation mais plutôt de faire un résumé des raccourcis basiques en TDD. Ainsi, nous omettrons volontairement les raccourcis de refactoring qui pourront faire l&#8217;objet d&#8217;un autre article, le refactoring étant indissociable du TDD. Pour connaitre tous les raccourcis, voici <a
href="http://hamletdarcy.blogspot.com/2010/10/eclipse-keyboard-shortcut-wallpaper.html" title="un lien avec poster" >un lien avec poster</a> donnant une liste complète pour Eclipse. A placarder chez tous vos clients ou équipes de développement!</p><p>Raccourcis à connaitre lorsque l&#8217;on démarre TDD (ou la programmation&#8230;) avec Eclipse.</p><fieldset><legend>DELETE LINE</legend><p>L&#8217;intérêt de ce raccourci est qu&#8217;il peut être exécuté n&#8217;importe où sur une ligne de code pour effacer cette dernière. Plus besoin d&#8217;aller en début ou bout de ligne ou d&#8217;utiliser la touche Shift avant d&#8217;effacer.</p><ul><li>Crtl + D (PC)</li><li>Cmd + D (Mac)</li></ul></fieldset><fieldset><legend>INSERT LINE BELOW CURRENT LINE</legend><p>Plus de touche Shift, End/Start of line&#8230; ce raccourci vous permet de n&#8217;importe où d&#8217;insérer une ligne au dessous avec la correcte indentation.</p><ul><li>Shift + Enter (PC &#038; Mac)</li></ul></fieldset><fieldset><legend>DUPLICATE</legend><p>Celui-ci est très utile pour dupliquer une ligne d&#8217;assertion ou encore des blocs de code qui sont communs à plusieurs tests.</p><ul><li>Ctrl + Alt + Up/Down arrow (PC)</li><li>Alt + Cmd + Up/Down arrow (Mac)</li></ul></fieldset><fieldset><legend>MOVE</legend><p>Pratique pour ré-organiser rapidement vos lignes ou blocs de code pour plus de lisibilité.</p><ul><li>Alt + Up/Down arrow (PC)</li><li>Alt + Cmd + Up/Down arrow (Mac)</li></ul></fieldset><fieldset><legend>QUICK FIX</legend><p>Ce raccourci est le raccourci à tout faire comme son nom l&#8217;indique. Il sera utile en TDD entre autre, pour la génération de variable (nom et type) ou conversion de variable de méthode en champ de classe. Par exemple, quand vous instanciez un objet, commencez toujours par écrire &#8216;new MonObjet();&#8217; et laissez ce raccourci faire le reste!</p><ul><li>Ctrl + 1 (PC)</li><li>A définir sur le Mac</li></ul></fieldset><fieldset><legend>ADD IMPORT</legend><p>Permet d&#8217;ajouter les <em>static imports</em> (transforme Assert.assertEquals en assertEquals, ou Mockito.when en when). Bien pratique pour Junit, Mockito, Hamcrest en vue de rendre les tests lisibles.</p><ul><li>Ctrl + Shift + M (PC)</li><li>Shift + Cmd + M (Mac)</li></ul></fieldset><p>A noter que pour gérer les <em>static imports</em> trés courant en TDD, le menu <em>Preferences/Java/Editor/Content Assist/Favorites</em> permet de définir une liste de membres static. Le raccourci <em>Content Assist</em> vous suggérera ensuite cette liste même si l&#8217;import est manquant.</p><h5>Key promoter</h5><p>Une bonne méthode pour vous inciter à préférer le clavier à la souris est d&#8217;utiliser ce que l&#8217;on appelle les <em>key promoter</em>.  Ce sont des plugins qui afficheront, au moyen d&#8217;une petite fenêtre, les raccourcis clavier à utiliser quand vous avez manqué à votre devoir de bon développeur! Voici les informations pour <a
href="http://blog.mousefeed.com/" title="Eclipse" >Eclipse</a> et <a
href="http://plugins.intellij.net/plugin/?id=1003" title="IntelliJ" >IntelliJ</a>.</p><h4><a
name="Unpeudergonomie"></a>Un peu d&#8217;ergonomie</h4><p>Eclipse vous offre une grande liberté quant à la disposition et organisation de vos vues (<i>View</i>) dans différentes perspective.</p><p>En TDD, la pensée et l&#8217;écriture du code font un va et vient entre la classe et sa classe de test correspondante. Cela peut paraitre un détail mais une façon de faciliter cela est de scinder votre vue éditeur java en deux comme le montre l&#8217;image suivante (cliquez pour agrandir).</p><div
align="center"> <a
href="http://blog.xebia.fr/wp-content/uploads/2010/10/tdd-split-screen.png"><img
src="http://blog.xebia.fr/wp-content/uploads/2010/10/tdd-split-screen-300x219.png" alt="tdd-split-screen" title="tdd-split-screen" width="300" height="219" class="alignnone size-medium wp-image-5772" /></a></div><p>Pour réaliser cela avec Eclipse, faites un <em>click &#038; drag</em> sur un onglet dans votre éditeur et amener le vers le bas (tout en maintenant le button de souris) jusqu&#8217;à ce que la séparation de l&#8217;écran apparaisse.</p><p>Vous pouvez ainsi visualiser le code et les tests correspondants en même temps. Une façon de s&#8217;organiser et par exemple d&#8217;avoir vos tests dans l&#8217;éditeur du bas et vos classes dans l&#8217;éditeur du haut.</p><h5>Eclipse Outline view</h5><p>Une vue très utile à l&#8217;organisation et visualisation des tests est la <em>Outline View</em>. Elle permet de voir rapidement tous les noms des méthodes de test et agit ainsi comme documentation pour une classe. Mais surtout elle permet, en utilisant un <em>drag &#038; drop</em> directement dans la vue, d&#8217;ordonner et de grouper logiquement les méthodes de test. Attention pour déplacer les méthodes de votre classe en utilisant la <em>Outline View</em>, le tri alphabétique doit être désactivé pour cette vue (petit bouton AZ dans le menu de la vue).</p><p>Ci-dessous un exemple de ré-organisation de méthodes</p><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/10/outline-view.png" border="0" alt="" /></div><h4><a
name="TemplatesdecodeJavapourTDD"></a>Templates de code Java</h4><p>Les templates de code sont une description structurée de patterns de code qui sont récurrents au cours de votre programmation. Eclipse vient par défaut avec une série de templates courants très utiles (<i>Preference > Java > Editor > Templates</i>), mais vous donne surtout les moyens de créer les vôtres avec beaucoup de flexibilité <a
href="http://help.eclipse.org/helios/topic/org.eclipse.jdt.doc.user/concepts/concept-template-variables.htm" title="grce aux variables" >grâce aux variables</a>. Notez que les templates de code peuvent être importés et exportés au format xml très facilement avec Eclipse. Ainsi il est facile de les partager au sein d&#8217;un groupe de développement.</p><p>Lors de la création d&#8217;un template vous devrez choisir un nom et une description. Le nom de votre template se voudra être court et mémorable et peut contenir un chiffre pour faciliter le filtrage.</p><p>Par exemple pour générer une méthode de test avec JUnit 4, j&#8217;ai appelé mon template <em>t4</em>, et je donne une courte description qui m&#8217;aidera lorsque j&#8217;aurai de nombreux templates.</p><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/10/template-creation.png" border="0" alt="" /></div><p>Pour ensuite utiliser le template, écrivez le nom du template et utilisez le raccourci du <i>Content Assist</i> (Ctrl + Space sur PC).</p><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/10/template-in-code.png" border="0" alt="" /></div><p>Voici quelques exemples de templates:</p><h5>t4 &#8211; Generate Junit4 test method with Mockito imports</h5><pre class="brush: java; title: ; notranslate">${staticImport:importStatic('org.junit.Assert.*')}${staticImport1:importStatic('org.mockito.Mockito.*')}${staticImport2:importStatic('org.mockito.MockitoAnnotations.Mock')}
@${:newType(org.junit.Test)}
public void should_${testname}() throws Exception {
	${cursor}
}</pre><h5>te &#8211; Generate Junit4 expected exception test method</h5><pre class="brush: java; title: ; notranslate">${:import('org.junit.Test')}
@${testType:newType(org.junit.Test)}(expected=${expectionname}.class)
public void should_throw_${testname}_when() throws Exception {
	${cursor}
}</pre><h5>b4 &#8211; Generate Junit4 setUp method with Mockito imports</h5><pre class="brush: java; title: ; notranslate">${before:import('org.junit.Before')}${initMocks:importStatic('org.mockito.MockitoAnnotations.*')}
@${:newType(org.junit.Before)}
public void initBeforeTest() throws Exception {
	initMocks(this);
	${cursor}
}</pre><h5>mk &#8211; Generate an instance field with the Mockito (@Mock ) annotation</h5><pre class="brush: java; title: ; notranslate">${:import(org.mockito.Mock)}
@${mockType:newType(org.mockito.Mock)} private ${dependencyType} ${name};</pre><h5>Variables pour templates</h5><p>La création de templates nécessite de savoir utiliser <a
href="http://help.eclipse.org/helios/topic/org.eclipse.jdt.doc.user/concepts/concept-template-variables.htm" title="les variables de templates" >les variables de templates</a> ce qui reste très simple. Il existe des variables génériques comme ${cursor} ou des variables définies par l&#8217;utilisateur qui peuvent donc être réutilisées dans le template. Ces dernières adoptent le format suivant:</p><ul><li>${<i>variableUtilisateur</i>:<i>operateurTemplate</i>)}</li></ul><p>Exemples:</p><ul><li>${mockType:newType(org.mockito.Mock)}</li><li>${:newType(org.junit.Before)}</li></ul><p>Le nom de la <em>variableUtilisateur</em> peut être laissé vide mais ce nom doit être unique pour chaque template (même vide) pour éviter les conflits.</p><p>Les templates trouvent leur sens lorsque la répétition est grande comme c&#8217;est le cas en TDD. Cependant, je pense qu&#8217;ils sont sous-estimés et encore trop peu utilisés. Faites place à votre imagination pour de nouvelles trouvailles et partagez-les au sein de votre équipe. La lassitude de coder et les tendinites devraient diminuer et la productivité augmentera.</p><h4><a
name="LepluginMoreUnit"></a>Le plugin MoreUnit</h4><p>Un plugin qui a commencé à réconcilier TDD et rendement est le plugin <a
href="http://moreunit.sourceforge.net/" title="MoreUnit" >MoreUnit</a> qui &#8211; après une période assez calme &#8211; est à nouveau en développement actif depuis l&#8217;été dernier. Une nouvelle version (2.2.0) sort d&#8217;ailleurs au moment où vous lisez ces lignes.</p><p>Voici les principales fonctionnalités de MoreUnit:</p><ol><li>Il décore d&#8217;une icône verte discréte toutes vos classes testées. Un moyen rapide de voir ce qui ne l&#8217;est pas!</li><li>Lors du renommage ou du déplacement d&#8217;une classe, MoreUnit se chargera de renommer/déplacer la classe de test correspondante.</li><li>Un raccourci (Ctrl+J) vous permet de sauter de votre classe à sa (ses) classe(s) de test, un autre (Ctr+R) de lancer le(s) test(s) à partir de la classe principale.</li><li>Ces mêmes raccourcis vous proposent de créer la classe testée en fonction de la classe de test (et vice-versa) si elle n&#8217;existe pas, le tout en suivant vos préférences (voir point suivant).</li><li>Le comportement plugin est configurable de sorte que vous puissiez utiliser vos propres conventions (quelques exemples: préfixe &laquo;&nbsp;should&nbsp;&raquo; pour les méthodes de test, méthodes de test nommées selon les méthodes testées, pré-/suffixe &laquo;&nbsp;ITest&nbsp;&raquo; pour des tests plus orientés intégration, packages de test pré-/suffixés par &laquo;&nbsp;test.&nbsp;&raquo;, tests placés dans un projet séparé, etc&#8230;).</li><li>Un dernier raccourci (Ctrl+U) vos permet de créer une méthode de test pour une méthode donnée, dans une classe de test existante et correspondant à la classe testée.</li><li>Enfin, MoreUnit supporte à la fois JUnit (3&#038;4) et TestNG.</li></ol><p>Cela fait maintenant plus de 2 ans que j&#8217;utilise moreUnit. Installez-le vous ne le regretterez pas.</p><h3><a
name="ObjetmtiersetBuilderPattern"></a>Objet métiers et Builder Pattern</h3><p>Une des tâches les plus ardues lorsque l&#8217;on pratique TDD consiste à générer les différentes instances des objets métiers qui seront utilisées pour les tests unitaires.</p><p>Imaginons que pour notre test unitaire nous ayons besoin d&#8217;une instance de la classe User comme suit</p><pre class="brush: java; title: ; notranslate">User user = new User();
user.setUsername(&quot;bob&quot;);
user.setSurname(&quot;Martin&quot;)
user.setEmail(&quot;bob@mail.com&quot;)
user.setPhone(&quot;+447735463526&quot;);
</pre><p>Le code ci-dessus est répétitif et n&#8217;aide pas à la lecture.</p><p>Une façon d&#8217;y remédier, en obtenant un code plus lisible et flexible, est d&#8217;utiliser le Builder Pattern. Nous obtenons ainsi des lignes de code simples comme celles-ci</p><pre class="brush: java; title: ; notranslate">User user = new UserBuilder().username(&quot;bob&quot;).surname(&quot;Martin&quot;).email(&quot;bob@mail.com&quot;)
                          .phone(&quot;+447735463526&quot;).toUser();
</pre><p>Si l&#8217;on veut juste un objet User vide ou l&#8217;un avec simplement un email, ce pattern le permet très simplement avec</p><pre class="brush: java; title: ; notranslate">
User userWithEmail = new UserBuilder().email(&quot;bob@mail.com&quot;).toUser();
User simpleUser = new UserBuilder().toUser();
</pre><p>Voici ci-dessous la classe UserBuilder. Il est important de noter que le champ d&#8217;action de cette classe est réduit aux tests seulement et qu&#8217;elle n&#8217;apparaîtra jamais dans du code de production. Typiquement, en suivant les conventions Maven, cette classe résidera dans <em>src/test/java</em>.</p><pre class="brush: java; title: ; notranslate">
public class UserBuilder {
	private String username;
	private String surname;
	private String email;
	private String phone;
	public UserBuilder username(String username){
		this.username = username;
		return this;
	}
	public UserBuilder surname(String surname){
		this.surname = surname;
		return this;
	}
	public UserBuilder email(String email){
		this.email = email;
		return this;
	}
	public UserBuilder phone(String phone){
		this.phone = phone;
		return this;
	}
	public User toUser(){
		User user = new User();
		user.setUsername(username);
		user.setSurname(surname);
		user.setEmail(email);
		user.setPhone(phone);
		return user;
	}
}
</pre><p>Il est parfois fastidieux de générer ce type de builder pour tous les objets métiers surtout s&#8217;ils sont imposants. Le <a
href="http://boss.bekk.no/bpep/index.html" title="plugin Eclipse suivant" >plugin Eclipse suivant</a> peut aider. Il génère un builder classique pour une classe donnée (la rendant par la même occasion immutable). Moyennant un peu de copier-coller en utilisant la classe produite par le plugin, on peut retrouver le builder présenté ci-dessus sans trop d&#8217;effort. Le site update de ce plugin Eclipse est <a
href="http://boss.bekk.no/bpep/update" title="ici" >ici</a>.</p><h3><a
name="ContinuousunittestingavecEclip"></a>Continuous unit testing avec Eclipse</h3><p>Le test unitaire en continu est encore peu utilisé mais est un outil très puissant et indispensable pour le TDD. Il permet de se focaliser uniquement sur l&#8217;implémentation et la conception des tests, car votre IDE se chargera de faire tourner les tests unitaires et vous alertera lorsqu&#8217;un test est dans le rouge.</p><p>Le cycle classique de programmation en TDD est</p><ol><li>Écriture du test</li><li>Lancer &#8211; par raccourci clavier &#8211; JUnit pour être dans le rouge</li><li>Écriture de l&#8217;implémentation</li><li>Lancer &#8211; par raccourci clavier &#8211; JUnit pour être dans le vert</li><li>Recommencer le cycle avec un autre test</li></ol><p>Avec le test unitaire en continu, intégré dans votre IDE, votre cycle devient</p><ol><li>Écriture du test</li><li>Écriture de l&#8217;implémentation</li><li>Recommencer le cycle avec un autre test</li></ol><p>On en gagne du temps! Votre IDE (ou plutôt le plugin) se charge du reste&#8230; et c&#8217;est bien normal. Les développeurs sont là pour la conception après tout!</p><p>Les principaux plugins sont:</p><ul><li><a
href="http://code.google.com/p/junitflux/" title="JUnit Flux" >JUnit Flux</a></li></ul><p>Plugin gratuit et très simple d&#8217;utilisation. Il a cependant des problème de stabilité car la mise à jour et le rafraîchissement dans la <em>JUnit View</em> ne se font pas toujours correctement après un certain temps d&#8217;utilisation. Cela pose des problèmes car votre test est dans le vert alors qu&#8217;il devrait être dans le rouge! Cependant, c&#8217;est idéal pour commencer.</p><ul><li><a
href="http://junitmax.com/" title="JUnit Max" >JUnit Max</a></li></ul><p>Développé par Kent Beck, il était tombé en désuétude mais a été remis sur le marché en Septembre 2010. Cependant, la licence reste chère à $100 par an! C&#8217;est peut être la raison pour laquelle je ne l&#8217;ai pas encore essayé. Je suis preneur de retour d&#8217;expérience sur ce plugin.</p><ul><li><a
href="http://improvingworks.com/products/infinitest/" title="Infinitest" >Infinitest</a></li></ul><p>Plugin stable au prix de $20. La notification de test en erreur se fait à l&#8217;aide de la <em>Problem View</em> (introduit par JUnit Max) ce qui veut dire que votre plugin ne tournera pas si vous avez une quelconque erreur de workspace. Un peu ennuyeux parfois. La configuration du plugin reste basique. Il n&#8217;y a en effet pas moyen de choisir sur quels projets dans votre workspace vous voulez appliquer le test continu. Ce qui veut dire qu&#8217;Infinitest, va faire tourner tous les tests de votre workspace. Le filtrage des classes de test par leur nom (pour éviter les tests d&#8217;intégration comme *.ITest) est un peu décevant puisque il faut ajouter un fichier de properties dans le projet au lieu d&#8217;utiliser un menu Eclipse dans les préférences.</p><p>Ayant commencé par utiliser JUnit Flux, j&#8217;utilise maintenant Infinitest et c&#8217;est celui que je recommanderais pour petit budget &#8211; vous pouvez l&#8217;essayer gratuitement pour 30 jours &#8211;  en attendant sur le marché un plugin gratuit plus satisfaisant ou d&#8217;avoir le temps pour en développer un !</p><h3><a
name="Conclusion"></a>Conclusion</h3><p>Aujourd&#8217;hui, le TDD est une arme que tout développeur Java (ou tout language qui s&#8217;y prête bien) doit avoir à son arsenal. Tout débat autour de cette méthodologie deviendrait inutile si tout le monde la maitrisait. En effet, chaque développeur, pourrait à son gré choisir de l&#8217;appliquer ou non en toute connaissance de cause.</p><p>Cependant, la maitrise du TDD demande du temps, de la méthode et surtout de la discipline.</p><p>Le but de cet article a été de promouvoir les pratiques indispensables. Elles permettent au développeur de se concentrer sur la partie <em>fun</em> du TDD en automatisant et en facilitant certaines tâches sans pour autant qu&#8217;il perde le contrôle.</p><p>Si la pratique du TDD n&#8217;est pas encore grandement répandue, j&#8217;espère que cet article aidera à faciliter son apprentissage.</p><div
class="shr-publisher-5762"></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div><div
class='shareaholic-like-buttonset' style='float:none;height:30px;'><a
class='shareaholic-googleplusone' data-shr_size='medium' data-shr_count='true' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2010%2F11%2F03%2Ftdd-et-productivite%2F' data-shr_title='TDD+et+productivit%C3%A9'></a><a
class='shareaholic-tweetbutton' data-shr_count='horizontal' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2010%2F11%2F03%2Ftdd-et-productivite%2F' data-shr_title='TDD+et+productivit%C3%A9'></a></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div>]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2010/11/03/tdd-et-productivite/feed/</wfw:commentRss> <slash:comments>8</slash:comments> </item> <item><title>Lumière sur JTestR</title><link>http://blog.xebia.fr/2009/10/02/lumiere-sur-jtestr/</link> <comments>http://blog.xebia.fr/2009/10/02/lumiere-sur-jtestr/#comments</comments> <pubDate>Fri, 02 Oct 2009 12:09:59 +0000</pubDate> <dc:creator>Aurélien Maury</dc:creator> <category><![CDATA[Tests]]></category> <category><![CDATA[JRuby]]></category> <category><![CDATA[Maven]]></category> <category><![CDATA[TDD]]></category> <category><![CDATA[Tests unitaires]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=2947</guid> <description><![CDATA[Parmi les tâches incontournables de la vie d&#8217;un développeur, il y a l&#8217;écriture des tests unitaires. De nombreux outils tentent de nous faciliter la vie sur ce point. Aujourd&#8217;hui, je vais vous parler de JTestR, un framework de tests unitaires qui apporte la puissance et la rapidité d&#8217;écriture du scripting Ruby pour tester des applications [...]]]></description> <content:encoded><![CDATA[<p>Parmi les tâches incontournables de la vie d&#8217;un développeur, il y a l&#8217;écriture des tests unitaires. De nombreux outils tentent de nous faciliter la vie sur ce point. Aujourd&#8217;hui, je vais vous parler de <a
href="http://jtestr.codehaus.org/">JTestR</a>, un framework de tests unitaires qui apporte la puissance et la rapidité d&#8217;écriture du scripting <a
href="http://www.ruby-lang.org/fr/">Ruby</a> pour tester des applications Java. Lancé par Ola Bini, un contributeur incontournable du projet JRuby, JTestR est directement intégrable avec Ant, <a
href="http://buildr.apache.org/" title="Buildr" >Buildr</a> et Maven 2. Ce projet n&#8217;est pas encore très répandu, mais il apporte des avantages qui méritent d&#8217;être étudiés.</p><h3><a
name="Tourdhorizon"></a>Tour d&#8217;horizon</h3><p>JTestR est un ensemble de librairies Ruby dédiées aux tests, alliées à JRuby pour permettre un démarrage rapide et sans douleur de l&#8217;écriture des tests. L&#8217;un des atouts principaux du scripting est la rapidité d&#8217;écriture, et Ruby est un langage de scripting devenu très populaire ces dernières années, notamment grâce à <a
href="http://rubyonrails.org/" title="Ruby on Rails" >Ruby on Rails</a>. Les tests unitaires et d&#8217;intégration font partie intégrante du monde Ruby et plusieurs outils ont vu le jour pour les faciliter. JTestR embarque les librairies de test Ruby suivantes :</p><ul><li><strong> <a
href="http://en.wikibooks.org/wiki/Ruby_Programming/Unit_testing" title="TestUnit" >Test/Unit</a></strong></li><li><strong> <a
href="http://rspec.info/" title="RSpec" >RSpec</a></strong></li><li><strong> <a
href="http://expectations.rubyforge.org/" title="Expectations" >Expectations</a></strong></li><li><strong> <a
href="http://dust.rubyforge.org/" title="Dust" >Dust</a></strong></li><li><strong> <a
href="http://mocha.rubyforge.org/" title="Mocha" >Mocha</a></strong></li></ul><p>En tant qu&#8217;outil Java, il permet également des interactions avec les frameworks :</p><ul><li><strong> <a
href="http://www.junit.org/" title="JUnit" >JUnit</a></strong></li><li><strong> <a
href="http://testng.org" title="TestNG" >TestNG</a></strong></li></ul><p>Dans sa dernière version, JTestR nécessite l&#8217;utilisation de Java 1.6.</p><h3><a
name="Intgration"></a>Intégration</h3><p>Livré sous la forme d&#8217;un jar avec une tâche Ant, il est facilement intégrable dans un projet existant. Une fois le jar disponible dans votre classpath, il suffit d&#8217;ajouter une balise <code>taskdef</code> à votre build.xml pour plus de confort et le tour est joué:</p><pre class="brush: xml; title: ; notranslate">
&lt;target name=&quot;test&quot; description=&quot;Runs all tests with default configuration of JTestR&quot;&gt;
  &lt;taskdef name=&quot;jtestr&quot; classname=&quot;org.jtestr.ant.JtestRAntRunner&quot; classpath=&quot;lib/jtestr.jar&quot;/&gt;
  &lt;jtestr/&gt;
&lt;/target&gt;
</pre><p>Pour les projets Maven, une modification mineure du pom.xml fera l&#8217;affaire :</p><pre class="brush: xml; title: ; notranslate">
&lt;!-- dans la section plugins --&gt;
  &lt;plugin&gt;
    &lt;groupId&gt;org.jtestr&lt;/groupId&gt;
    &lt;artifactId&gt;jtestr&lt;/artifactId&gt;
    &lt;version&gt;0.4.0&lt;/version&gt;
    &lt;configuration&gt;
      &lt;tests&gt;src/test/ruby&lt;/tests&gt;
    &lt;/configuration&gt;
    &lt;executions&gt;
      &lt;execution&gt;
        &lt;goals&gt;
          &lt;goal&gt;test&lt;/goal&gt;
        &lt;/goals&gt;
      &lt;/execution&gt;
    &lt;/executions&gt;
  &lt;/plugin&gt;
</pre><p>La directive de configuration <code>tests</code> permet de spécifier le répertoire dans lequel chercher les scripts de tests. Pour conserver l&#8217;esprit Maven, je vous conseille de créer un répertoire <code>src/test/ruby</code> et d&#8217;y placer tous les tests. La hiérarchie en sous répertoires pour représenter les packages des classes à tester ne pose pas de soucis.</p><h3><a
name="Rdactiondestests"></a>Rédaction des tests</h3><p>JTestR offre plusieurs possibilités pour la rédaction des tests. Tout d&#8217;abord, l&#8217;utilisation du framework de tests académiques de Ruby :<code>Test::Unit</code>. Cela n&#8217;apporte pas de grande nouveauté par rapport aux classiques tests JUnit. Le couteau suisse de méthode <code>assert</code> est disponible de la même façon.</p><p>Exemple de Test::Unit</p><pre class="brush: ruby; title: ; notranslate">
class HashMapTests &lt; Test::Unit::TestCase
  def setup
    @map = java.util.HashMap.new
  end
  def test_that_map_is_empty
    assert @map.isEmpty
  end
  def test_that_it_returns_a_keyset_that_returns_an_iterator_that_throws_exception
    assert_raises(java.util.NoSuchElementException) do
      @map.key_set.iterator.next
    end
  end
end
</pre><p>Mais il est également possible d&#8217;utiliser <a
href="http://rspec.info/">RSpec</a>. Ce framework de tests est orienté &laquo;&nbsp;spécification&nbsp;&raquo; et propose une notation originale qui tend à rapprocher la rédaction des tests de la spécification du comportement attendu. La notation comparée :</p><pre class="brush: ruby; title: ; notranslate">
# avec Test::Unit
assert_equals(result, 42)
# avec RSpec
result.should == 42
</pre><p>On se rapproche ainsi du langage naturel. En anglais dans le texte, certes, mais on se rapproche. La structure d&#8217;un fichier RSpec complet donne par exemple :</p><pre class="brush: ruby; title: ; notranslate">
import java.util.HashMap
describe &quot;An empty&quot;, HashMap do
  before :each do
    @hash_map = HashMap.new
  end
  it &quot;should not be empty after an entry has been added to it&quot; do
    @hash_map.put &quot;foo&quot;, &quot;bar&quot;
    @hash_map.should_not be_empty
  end
  it &quot;should be empty&quot; do
    @hash_map.should be_empty
    @hash_map.size.should == 0
  end
  it &quot;should return a keyset iterator that throws an exception on next&quot; do
    proc do
      @hash_map.key_set.iterator.next
    end.should raise_error(java.util.NoSuchElementException)
  end
end
</pre><p>Il est possible de mixer dans le même projet des tests Test::Unit et des tests RSpec. Le respect d&#8217;une norme de nommage des fichiers suffit à JTestR pour déterminer le framework ciblé.</p><p><em>Une précision importante sur un point obscur dans la documentation :</em> la commande <code>import</code> cherche dans les packages commençant par <code>java</code>, <code>javax</code>, <code>org</code> et <code>com</code>. Pour tous les autres il faut écrire :</p><pre class="brush: ruby; title: ; notranslate">
import Java::fr.votre.package.VotreClasse
</pre><h3><a
name="Limitations"></a>Limitations</h3><p>Un problème qui peut être pointé du doigt est le temps de chargement de l&#8217;environnement JRuby. L&#8217;interpréteur prend en effet parfois plus de 10 secondes à se lancer. Pour pallier ce problème, JTestR possède un mode serveur qui permet de conserver en tâche de fond un serveur avec l&#8217;environnement JRuby en attente des tests à lancer. On supprime ainsi facilement le temps de chargement.</p><p>L&#8217;apprentissage des bases du langage Ruby peut également rebuter, mais c&#8217;est un obstacle faible dans la mesure où c&#8217;est un langage entièrement objet et où l&#8217;on manipule majoritairement les classes Java du projet à tester. Le pas est généralement franchi <a
href="http://www.ruby-lang.org/fr/documentation/ruby-from-other-languages/to-ruby-from-java/" title="assez facilement" >assez facilement</a>.</p><h3><a
name="Conclusions"></a>Conclusions</h3><p>L&#8217;offre de frameworks de tests est déjà <em>conséquente</em> dans le monde Java. JTestR n&#8217;est pas très répandu à l&#8217;heure actuelle. Malgré cela, l&#8217;utilisation de RSpec pour la rédaction des tests permet de rester proche d&#8217;une formulation verbale des comportements attendus. Dans une démarche TDD, cela peut être avantageux. On aura ainsi tendance à éviter de se perdre dans des formulations techniques dès les premières phases du projet.</p><p>De plus, je prendrai sérieusement en considération JTestR si j&#8217;avais des développeurs Ruby dans mon équipe. Le framework vous offre une chance de casser la frontière entre vos équipes Ruby et Java. L&#8217;aspect très concis des tests les rend lisibles autant par les développeurs Java que Ruby et renforce l&#8217;entraide entre équipes. Cela facilite la distribution des compétences et des connaissances entre développeurs, ce qui n&#8217;est jamais une mauvaise chose.</p><p><strong>Ressources :</strong></p><ul><li><a
href="http://jtestr.codehaus.org/" title="Le site officiel JTestR" >Le site officiel JTestR</a></li><li><a
href="http://jtestr.codehaus.org/Getting+Started" title="La documentation" >La documentation</a></li><li><a
href="http://rspec.info/" title="RSpec" >RSpec</a></li><li><a
href="http://ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html" title="TestUnit" >Test::Unit</a></li></ul><div
class="shr-publisher-2947"></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div><div
class='shareaholic-like-buttonset' style='float:none;height:30px;'><a
class='shareaholic-googleplusone' data-shr_size='medium' data-shr_count='true' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2009%2F10%2F02%2Flumiere-sur-jtestr%2F' data-shr_title='Lumi%C3%A8re+sur+JTestR'></a><a
class='shareaholic-tweetbutton' data-shr_count='horizontal' data-shr_href='http%3A%2F%2Fblog.xebia.fr%2F2009%2F10%2F02%2Flumiere-sur-jtestr%2F' data-shr_title='Lumi%C3%A8re+sur+JTestR'></a></div><div
style="clear: both; min-height: 1px; height: 3px; width: 100%;"></div>]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2009/10/02/lumiere-sur-jtestr/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> </channel> </rss>
