CDI et l’injection de mock pour les tests unitaires

April 18th, 2011 No comments

L’injection de dépendances avec CDI me semble vraiment très intéressante en terme d’usabilité et de lisibilité du code.
La seule chose qui me dérange un peu est le manque de facilité à tester un objet utilisant l’injection de dépendances. Ou, plutôt, comment utiliser un framework de mock et injecter un mock sur lequel j’ai la main en test.
Je préviens d’avance. J’ai trouvé quelques d’articles qui gravitent autour de ce sujet et j’en ai fait un mix pour arriver à des solutions. Mais, à ce jour, je n’ai rien trouvé qui me semble simple et élégant à utiliser… (du moins, c’est perfectible)
Voyons voir sur un exemple simple comment procéder pour arriver à utiliser un framework tel que Mockito.
Tout d’abord, voilà mes classes et l’interface côté implémentation.
L’interface représentant le service que je vais vouloir mocker par la suite.

public interface MyService {

    String foo();
}

Son implémentation par défaut :

public class DefaultMyService implements MyService {

    public String foo() {
        return "DefaultMyService.foo()";
    }
}

La classe dans laquelle le service est injecté par constructeur :

public class MyApplication {

    private MyService myService;

    @Inject public MyApplication(MyService myService) {
        this.myService = myService;
    }

    public void run() {
        System.out.println("myService.foo() : " + myService.foo());
    }
}

Voilà le test le plus basic que vous puissiez faire :

public class MyApplicationTest {

    @Test public void shouldAppWorkWithMocks() {
        WeldContainer weld = new Weld().initialize();
        MyApplication app = weld.instance().select(MyApplication.class).get();
        app.run();
    }
}

Il n’y a pas à dire, les 2 premières lignes du test ne sont pas très élégantes… Imaginez que je les répète… (copier/coller : booouuuhh, c’est mal ! il y en a qui ont perdu des doigts comme ça ! ) Non, je déconne, pas de copier/coller, c’est le mal !
De plus, si j’avais dupliqué ce test, vous auriez eu une initialisation du container à chaque test. Il doit y avoir moyen de faire mieux, non ?
En cherchant un peu à résoudre ces soucis, je suis tombé sur ce très bon article du blog d’Alexis Hassler. Il utilise un Runner JUnit qui lui permet d’initialiser le container avec la classe à tester (il a aussi développé une Rule JUnit si vous êtes contraint d’utiliser un Runner particulier). Voilà à quoi ressemble son Runner :

public class WeldRunner extends BlockJUnit4ClassRunner {

    private Weld weld;
    private WeldContainer container;

    public WeldRunner(Class klass) throws InitializationError {
        super(klass);
    }

    @Override public void run(RunNotifier notifier) {
        initializeWeld();
        super.run(notifier);
        shutdownWeld();
    }

    @Override protected Object createTest() throws Exception {
        return container.instance().select(getTestClass().getJavaClass()).get();
    }

    private void initializeWeld() {
        weld = new Weld();
        container = weld.initialize();
    }

    private void shutdownWeld() {
        weld.shutdown();
    }
}

Y’a pas à dire, c’est simple, c’est beau !

Utilisons donc sont Runner. La classe de test devient alors :

@RunWith(WeldRunner.class)
public class MyApplicationRunnerTest {

    @Inject private MyApplication app;

    @Test public void shouldAppWorkWithMocks() {
        app.run();
    }
}

C’est déjà beaucoup plus lisible et améliore clairement l’usabilité.

Seule chose gênante, je n’ai pas la main sur l’instance de MyService injectée sur MyApplication. Donc, si je veux isoler MyApplication pour faire un test unitaire, je dois bouchonner l’instance injectée. Alors comment faire ?
1ère solution : La solution la plus simple serait d’avoir un setter pour injecter l’instance de MyService. Or, ça ne me plaît pas de retomber dans les travers (de porc) d’autres framework d’injection ou vous êtes forcé d’ouvrir votre code pour qu’il fonctionne. CDI permet de faire de l’injection sans cette “contrainte” au niveau du design du code donc pourquoi créer un setter. J’avoue que cette solution ne me plaît pas trop…
2ème solution : utiliser une alternative à l’implémentation par défaut de MyService en créant manuellement un mock qui sera alors injecté pour chacun de vos cas de test. Cela peut être intéressant mais c’est assez contraignant tout de même car vous allez sans doute devoir tester différents comportements de votre mock. Voyons voir comment cela se fait.
Tout d’abord, je vais créer une annotation pour annoter mes bouchons manuels que je souhaite injecter dans le container.

@Alternative
@Stereotype
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface MockAlternative {
}

Dans le scope de test, voilà le contenu du fichier META-INF/beans.xml (ou WEB-INF/beans.xml) pour que CDI prenne en compte cette Alternative :

<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">

    <alternatives>
        <stereotype>fr.stateofmind.cdi.test.annotation.MockAlternative</stereotype>
    </alternatives>

</beans>

Définissez alors votre mock décoré de l’Alternative :

@MockAlternative
public class MyServiceMock implements MyService {

    public String foo() {
        return "MyServiceMock.foo()";
    }
}

Et voilà, si vous rejouez le test, c’est bien le mock qui a été injecté.

3ème solution : c’est la solution qui me semble la plus pratique au jour le jour si on ne souhaite pas introduire de setter. Néanmoins, je ne la trouve pas super jolie, un peu trop verbeuse. Cette solution est en fait un complément de la solution précédente qui consiste à aller plus loin en injectant une instance mockée dans le container et de garder la main sur cette instance mockée. Avec cette solution, vous pourrez alors utiliser votre framework de mock favori. Ici, je compte utiliser Mockito.

@RunWith(WeldRunner.class)
public class MyApplicationRunnerTest {

    @Inject private MyApplication app;
    @Produces @MockAlternative private MyService myMockService = mock(MyService.class);

    @Before public void setUp() throws Exception {
        when(myMockService.foo()).thenReturn("My Mock !!!!");
    }

    @Test public void shouldAppWorkWithMocks() {
        app.run();
    }
}

Voilà ! Maintenant, vous avez la main sur le mock et vous pouvez vraiment faire ce que vous voulez dans votre test en gérant cas nominaux et cas limites.

J’espère que ce petit article vous aura éclairé sur l’injection avec CDI dans le contexte des tests unitaires.

Je ne sais pas vous mais j’aime vraiment bien la dernière solution. Mais j’aurais préféré une écriture plus compacte en utilisant une simple annotation magique sur l’attribut myMockService sans faire appel manuellement à Mockito. Par contre, je n’ai pas trouvé comment faire. *Si quelqu’un sait comment faire, je suis preneur !!!*

Categories: Java Tags:

p6spy : connaître les requêtes passées à une base de données

February 27th, 2011 No comments

J’ai plusieurs fois été face à des bugs sans savoir exactement les requêtes qui étaient vraiment passées à ma base de données. Vous pouvez par exemple avoir le cas en utilisant Hibernate qui bien qu’affichant les requêtes n’affiche pas les paramètres qui lui sont passées (c’est dommage…). De même, avec des outils comme DbUnit, il vous sera parfois utile de savoir ce qui se passe réellement au niveau des requêtes.
J’ai donc cherché et j’ai trouvé p6spy, un très ancien projet, tellement ancien que le site officiel ne semble plus en ligne. La dernière release date de décembre 2003 mais cela fonctionne encore très bien.

p6spy est un proxy JDBC qui intercepte les requêtes passées à un SGBD et qui les logguent. Comme le site est un peu down, la documentation manque un peu. Je vous propose donc de voir comment l’utiliser dans un projet Maven.
Le projet est certes ancien mais les jar sont présents dans les dépôts officiels Maven2. Voilà comment vous devrez déclarer la dépendance :

        <dependency>
            <groupId>p6spy</groupId>
            <artifactId>p6spy</artifactId>
            <version>1.3</version>
        </dependency>

Pour configurer p6spy, il vous faudra créer un fichier de configuration spy.properties dans les resources de votre projet. Ce fichier se base sur un template fourni lorsqu’on télécharge les sources du projet. Voilà à quoi ressemble ce fichier lorsqu’on utilise MySql :

#################################################################
# P6Spy Options File                                            #
# See documentation for detailed instructions                   #
#################################################################

#################################################################
# MODULES                                                       #
#                                                               #
# Modules provide the P6Spy functionality.  If a module, such   #
# as module_log is commented out, that functionality will not   #
# be available.  If it is not commented out (if it is active),  #
# the functionality will be active.                             #
#                                                               #
# Values set in Modules cannot be reloaded using the            #
# reloadproperties variable.  Once they are loaded, they remain #
# in memory until the application is restarted.                 #
#                                                               #
#################################################################

module.log=com.p6spy.engine.logging.P6LogFactory
#module.outage=com.p6spy.engine.outage.P6OutageFactory

#################################################################
# REALDRIVER(s)                                                 #
#                                                               #
# In your application server configuration file you replace the #
# "real driver" name with com.p6spy.engine.P6SpyDriver. This is #
# where you put the name of your real driver P6Spy can find and #
# register your real driver to do the database work.            #
#                                                               #
# If your application uses several drivers specify them in      #
# realdriver2, realdriver3.  See the documentation for more     #
# details.                                                      #
#                                                               #
# Values set in REALDRIVER(s) cannot be reloaded using the      #
# reloadproperties variable.  Once they are loaded, they remain #
# in memory until the application is restarted.                 #
#                                                               #
#################################################################

# oracle driver
# realdriver=oracle.jdbc.driver.OracleDriver

# mysql Connector/J driver
realdriver=com.mysql.jdbc.Driver

# informix driver
# realdriver=com.informix.jdbc.IfxDriver

# ibm db2 driver
# realdriver=COM.ibm.db2.jdbc.net.DB2Driver

# the mysql open source driver
#realdriver=org.gjt.mm.mysql.Driver

#specifies another driver to use
realdriver2=
#specifies a third driver to use
realdriver3=

#the DriverManager class sequentially tries every driver that is
#registered to find the right driver.  In some instances, it's possible to
#load up the realdriver before the p6spy driver, in which case your connections
#will not get wrapped as the realdriver will "steal" the connection before
#p6spy sees it.  Set the following property to "true" to cause p6spy to
#explicitily deregister the realdrivers
deregisterdrivers=true

################################################################
# P6LOG SPECIFIC PROPERTIES                                    #
################################################################
# no properties currently available

################################################################
# EXECUTION THRESHOLD PROPERTIES                               #
################################################################
# This feature applies to the standard logging of P6Spy.       #
# While the standard logging logs out every statement          #
# regardless of its execution time, this feature puts a time   #
# condition on that logging.  Only statements that have taken  #
# longer than the time specified (in milliseconds) will be     #
# logged.  This way it is possible to see only statements that #
# have exceeded some high water mark.                          #
# This time is reloadable.                                     #
#
# executionthreshold=integer time (milliseconds)
#
executionthreshold=

################################################################
# P6OUTAGE SPECIFIC PROPERTIES                                 #
################################################################
# Outage Detection
#
# This feature detects long-running statements that may be indicative of
# a database outage problem. If this feature is turned on, it will log any
# statement that surpasses the configurable time boundary during its execution.
# When this feature is enabled, no other statements are logged except the long
# running statements. The interval property is the boundary time set in seconds.
# For example, if this is set to 2, then any statement requiring at least 2
# seconds will be logged. Note that the same statement will continue to be logged
# for as long as it executes. So if the interval is set to 2, and the query takes
# 11 seconds, it will be logged 5 times (at the 2, 4, 6, 8, 10 second intervals).
#
# outagedetection=true|false
# outagedetectioninterval=integer time (seconds)
#
outagedetection=false
outagedetectioninterval=

################################################################
# COMMON PROPERTIES                                            #
################################################################

# filter what is logged
filter=false

# comma separated list of tables to include when filtering
include     =
# comma separated list of tables to exclude when filtering
exclude     =

# sql expression to evaluate if using regex filtering
sqlexpression =

# turn on tracing
autoflush   = true

# sets the date format using Java's SimpleDateFormat routine
dateformat=

#list of categories to explicitly include
includecategories=

#list of categories to exclude: error, info, batch, debug, statement,
#commit, rollback and result are valid values
excludecategories=info,debug,result,batch

#allows you to use a regex engine or your own matching engine to determine
#which statements to log
#
#stringmatcher=com.p6spy.engine.common.GnuRegexMatcher
#stringmatcher=com.p6spy.engine.common.JakartaRegexMatcher
stringmatcher=

# prints a stack trace for every statement logged
stacktrace=false
# if stacktrace=true, specifies the stack trace to print
stacktraceclass=

# determines if property file should be reloaded
reloadproperties=false
# determines how often should be reloaded in seconds
reloadpropertiesinterval=60

#if=true then url must be prefixed with p6spy:
useprefix=false

#specifies the appender to use for logging
#appender=com.p6spy.engine.logging.appender.Log4jLogger
appender=com.p6spy.engine.logging.appender.StdoutLogger
#appender=com.p6spy.engine.logging.appender.FileLogger

# name of logfile to use, note Windows users should make sure to use forward slashes in their pathname (e:/test/spy.log) (used for file logger only)
#logfile     = spy.log

# append to  the p6spy log file.  if this is set to false the
# log file is truncated every time.  (file logger only)
append=true

#The following are for log4j logging only
#log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
#log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
#log4j.appender.STDOUT.layout.ConversionPattern=p6spy - %m%n

#log4j.appender.CHAINSAW_CLIENT=org.apache.log4j.net.SocketAppender
#log4j.appender.CHAINSAW_CLIENT.RemoteHost=localhost
#log4j.appender.CHAINSAW_CLIENT.Port=4445
#log4j.appender.CHAINSAW_CLIENT.LocationInfo=true

log4j.logger.p6spy=INFO,STDOUT

#################################################################
# DataSource replacement                                        #
#                                                               #
# Replace the real DataSource class in your application server  #
# configuration with the name com.p6spy.engine.spy.P6DataSource,#
# then add the JNDI name and class name of the real 		#
# DataSource here	            				#
#                                                               #
# Values set in this item cannot be reloaded using the          #
# reloadproperties variable.  Once it is loaded, it remains     #
# in memory until the application is restarted.                 #
#                                                               #
#################################################################
#realdatasource=/RealMySqlDS
#realdatasourceclass=com.mysql.jdbc.jdbc2.optional.MysqlDataSource

#################################################################
# DataSource properties                                         #
#                                                               #
# If you are using the DataSource support to intercept calls    #
# to a DataSource that requires properties for proper setup,    #
# define those properties here. Use name value pairs, separate  #
# the name and value with a semicolon, and separate the         #
# pairs with commas.                                            #
# 					                        #
# The example shown here is for mysql 	                        #
#                                                               #
#################################################################
#realdatasourceproperties=port;3306,serverName;ibmhost,databaseName;mydb

#################################################################
# JNDI DataSource lookup                                        #
#                                                               #
# If you are using the DataSource support outside of an app     #
# server, you will probably need to define the JNDI Context     #
# environment.                                                  #
#                                                               #
# If the P6Spy code will be executing inside an app server then #
# do not use these properties, and the DataSource lookup will   #
# use the naming context defined by the app server.             #
#                                                               #
# The two standard elements of the naming environment are	#
# jndicontextfactory and jndicontextproviderurl. If you need    #
# additional elements, use the jndicontextcustom property.      #
# You can define multiple properties in jndicontextcustom,      #
# in name value pairs. Separate the name and value with a       #
# semicolon, and separate the pairs with commas.                #
#                                                               #
# The example shown here is for a standalone program running on #
# a machine that is also running JBoss, so the JDNI context     #
# is configured for JBoss (3.0.4).                              #
#                                                               #
#################################################################
#jndicontextfactory=org.jnp.interfaces.NamingContextFactory
#jndicontextproviderurl=localhost:1099
#jndicontextcustom=java.naming.factory.url.pkgs;org.jboss.nameing:org.jnp.interfaces

#jndicontextfactory=com.ibm.websphere.naming.WsnInitialContextFactory
#jndicontextproviderurl=iiop://localhost:900

Ensuite, il vous faudra utiliser le driver de p6spy au lieu de votre driver JDBC classique. Voilà, par exemple, à quoi ressemble une configuration JPA2. Le fichier persistence.xml utilisant ce driver ressemblera à cela :

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">

    <persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>

        <class>fr.stateofmind.jpa.bean.Department</class>
        <class>fr.stateofmind.jpa.bean.Employee</class>
        <class>fr.stateofmind.jpa.bean.Adress</class>
        <class>fr.stateofmind.jpa.bean.Document</class>

        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect"/>
            <property name="hibernate.connection.driver_class" value="com.p6spy.engine.spy.P6SpyDriver"/>
            <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/snippetjpa2"/>
            <property name="hibernate.connection.username" value="user"/>
            <property name="hibernate.connection.password" value="password"/>

            <property name="hibernate.hbm2ddl.auto" value="create"/>
            <property name="hibernate.show_sql" value="false"/>
        </properties>
    </persistence-unit>

</persistence>

Voilà, désormais, vous voyez passer toutes les requêtes SQL et leurs paramètres en clair.

Categories: Java, maven Tags:

JUnit / Maven : Parallélisation de l’exécution des tests

February 26th, 2011 No comments


Faire des tests, c’est très bien. Cela permet d’améliorer la qualité globale d’un projet et cela évite aussi de nombreuses régressions.
Par contre, sur des projets anciens, le nombre de tests peut devenir très conséquent et donc l’exécution des tests peut devenir très longue.
Pour combler ce problème de lenteur dû à l’exécution de nombreux tests, il est possible d’exécuter les tests en parallèle.
Depuis JUnit 4.7 et Surefire 2.5 (pas certain de la version), il est possible de configurer Maven pour pouvoir exécuter vos tests unitaires de manière parallélisée. Pour cela, il faut configurer le harnais de test en jouant sur la configuration du plugin maven-surefire-plugin. Vous pouvez définir le nombre de threads dédiés à l’exécution des tests. avez 3 façons de faire pour paralléliser vos tests :

  • parallélisation de l’exécution des méthodes de tests au sein d’une classe
  •     </build>
    ...
            <pluginManagement>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <configuration>
                            <parallel>methods</parallel>
                            <threadCount>4</threadCount>
                        </configuration>
                    </plugin>
                </plugins>
            </pluginManagement>
    ...
        </build>
    
  • parallélisation de l’exécution des classes de tests
  •     </build>
    ...
            <pluginManagement>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <configuration>
                            <parallel>classes</parallel>
                            <threadCount>4</threadCount>
                        </configuration>
                    </plugin>
                </plugins>
            </pluginManagement>
    ...
        </build>
    
  • parallélisation en mixant les 2 façons précédentes
  •     </build>
    ...
            <pluginManagement>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <configuration>
                            <parallel>both</parallel>
                            <threadCount>4</threadCount>
                        </configuration>
                    </plugin>
                </plugins>
            </pluginManagement>
    ...
        </build>
    

Les performances obtenues dépendent grandement de la nature de votre projet, je vous conseillerais de faire quelques essais afin de configurer au mieux votre projet. La parallélisation des tests vous demandera peut-être de revoir l’implémentation de votre code car, si vous avez des ressources partagées et donc si il y a des dépendances entre vos tests, certains de vos tests pourront échouer. Je vous conseille de corriger ces problèmes car l’effort est vraiment rentable vis-à-vis du gain en performance.

Categories: Java, maven Tags:

Install Inconsolata.ttf in Ubuntu

September 19th, 2010 No comments


Incosolata est une police de caractères assez connue par les développeurs de par sa très bonne lisibilité. J’ai eu quelques soucis à pouvoir l’installer et l’utiliser sous Ubuntu avec IntelliJ.
Dans un premier temps, j’ai voulu installer la police à partir du dépôt Ubuntu :
sudo apt-get install ttf-inconsolata
Mais lorsque j’ai essayé de changer de police de caractères sous IntelliJ, la police Inconsolata n’était pas proposée dans la liste des polices reconnues.Précisions importante, IntelliJ est exécuté à partir du JDK 6 Sun.
La commande d’installation de Inconsolata a installé le fichier suivant : /usr/share/fonts/truetype/ttf-inconsolata/Inconsolata.otf
On aurait pu s’attendre à une police ttf… C’est pour cela qu’IntelliJ ne vous la propose pas (sous Ubuntu). Il nous faut donc convertir cette police en ttf. Pour cela, soit vous disposez d’une application de conversion soit vous pouvez utiliser le site http://onlinefontconverter.com. Une fois convertie, recopier le fichier Inconsolata.ttf dans le répertoire /usr/share/fonts/truetype/ttf-inconsolata puis réinitialisez le cache des polices de caractères par la commande suivante :
sudo fc-cache -f -v
Vous pourrez alors désormais utiliser la police Inconsolata sous IntelliJ.
Pour info, je ne suis pas certain du tout que cette procédure fonctionne lorsque IntelliJ utilise OpenJDK…

Categories: IntelliJ, Java, Linux Tags: , ,

OpenGrok

June 28th, 2010 No comments


OpenGrok est un moteur de recherche facile d’utilisation et rapide permettant d’indexer du code. C’est un projet qui peut être utilisé dans un navigateur (ou en ligne de commande si vous êtes un peu masochistes). La partie graphique est accessible grâce à un simple navigateur web. Pour cela, il vous faudra déployer une archive war sur un serveur d’application tel que Tomcat.
OpenGrok permet de faire de simples recherches ou des recherches plus complexes en se basant sur la syntaxe de Google. Il est possible d’indexer plusieurs projets et d’exécuter des recherches parmi un ou plusieurs projets. Des recherches plus restrictives peuvent être faites pour par exemple n’exécuter les recherches que dans un répertoire donné. La navigation au sein du code est elle aussi possible. L’outil gère aussi la coloration syntaxique, la navigation dans les arborescences projets … L’indexation des projets peut se faire en utilisant de nombreux outils de versioning tels que Git, Mercurial, SVN, CVS, … Bref, si vous recherchez un outil pour indexer votre code, c’est sans doute l’outil qu’il vous faut. Après avoir essayé d’autres outils du même genre (et il n’y en a pas beaucoup), d’après moi, aucun n’arrive à la cheville du projet OpenGrok.
Néanmoins, si j’avais une critique à faire à propos de ce projet, ce serait que la documentation n’est pas vraiment à jour ou manque de clarté. La conséquence à cela étant que l’installation est souvent un problème qui fait que l’on abandonne souvent avant même de l’avoir utilisé… C’est la raison pour laquelle je vais vous présenter un tutorial permettant l’installation d’OpenGrok.

Tutorial d’installation dOpenGrok


Site officiel : http://hub.opensolaris.org/bin/view/Project+opengrok

Categories: Java, Tools Tags: , ,