Jean-Yves, Bricolage et Bidouilles

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

jeudi 14 décembre 2006

Test unitaires et Socket

La litérature déborde d'informations sur la façon de faire des test unitaires sur des classes simples et autonomes.

Lorsque l'on creuse un peu plus on arrive vite à avoir besoin d'utiliser des objets factices ou encore 'Mock Object', ce que encore une fois l'on fait facilement grâce à des librairies comme JMock, que je ne saurais trop conseiller d'ailleurs car son utilisation, une fois passé l'écueil de la documentation un peu sommaire, est assez facile.

Après avoir passé quelques heures à tester et à implémenter des classes simples, on se retrouve à implémenter les couches basses de l'application qui font appel à des librairies non conçues pour les tests unitaires (n'utilisant pas d'interfaces).

Comment procéder lorsque l'on doit tester d'une manière unitaire le code suivant ?

(Ce n'est pas forcément l'implémentation optimum d'un client TCP, mais disons que cet exemple est la pour illustrer la méthode de test :)).

   public boolean sendMessage(byte message, int length) {
       Socket socket = null;
       try {
           socket = new socket(inetAddress,portnumber);
           OutputStream outPut =socket.getOutputStream();
           outPut.write(message, 0, length);
           outPut.close();
       } catch (IOException e) {
           return false;
       }
       return true;
   }

La classe Socket et OutputStream, ne sont pas des implémentations d'interface, et il est donc impossible d'utiliser JMock pour vérifier que leur appel est bien effectué, ou de simuler le comportement de problème réseau qui peuvent lever des IOException. Pourtant il serait bien pratique de le faire et éviterai d'avoir à tester manuellement les cas de connexion et déconnexion réseau en devant enlever ou remettre le câble réseau de la machine.

Première solution, on laisse tel quel et on testera à la main.

Deuxième solution, qui une fois que l'on a trouvé la façon de faire ne parait pas si difficile, ecrire soit même des object factices qui seront renvoyé par une 'SocketFactory' que l'on pourra elle même utiliser d'une manière factice grâce à JMock. Ces objets héritent des objets que l'on ne peut pas tester et l'on peut facilement surcharger les méthodes que l'on veut pouvoir simuler.

  • Ecrire la classe factory qui renverra l'implémentation de la socket
   public class SocketFactoryImpl implements SocketFactory {
   
       public Socket createSocket(InetAddress address, int port)
                           throws IOException {
   
       return new Socket(address,port);
       }
   
   }
  • Utiliser cette factory pour renvoyer la Socket à la classe utilisatrice.
   protected boolean sendMessage(byte message, int length) {
       Socket socket = null;
       try {
           socket = socketFactory.createSocket(inetAddress, portNumber);
           OutputStream outPut =socket.getOutputStream();
           outPut.write(message, 0, length);
           outPut.close();
       } catch (IOException e) {
           return false;
       }
       return true;
   }
  • Ecrire dans le test unitaire des classes factices Socket et OutputStream
   private class MockOutputStream extends OutputStream {
       
       public  boolean writeCalled = false;
       public  boolean closeCalled = false;
       public boolean sendException = false;
       
       public void write(byte b, int off, int len) throws IOException {
           if (sendException) throw new IOException("MockOutputStream exception");
           writeCalled = true;
       }
       
       public void close() throws IOException {
           closeCalled = true;
       }
       /* (non-Javadoc)
        * @see java.io.OutputStream#write(int)
        */
       @Override
       public void write(int b) throws IOException {
           
       }
       
   }
   private class MockSocket extends Socket {
       
       public boolean sendException = false;
       public boolean closeCalled = false;
       private OutputStream outputStream;
       
       public void setOutputStream(OutputStream outputStream) {
           this.outputStream = outputStream;
       }
       public OutputStream getOutputStream() throws IOException {
           if (sendException) throw new IOException("mock exception");
           return outputStream;
       }
       
       public synchronized void close() throws IOException {
           if (sendException) throw new IOException("mock exception");
           closeCalled = true;
       }
       
   }

Une fois ces deux classes écrite il est facile de les utiliser pour simuler un comportement réseau hératique.

Exemple :

   public void testSendMessage() {
       MockOutputStream mockOutputStream = new MockOutputStream();
       MockSocket mockSocket = new MockSocket();
       mockSocket.setOutputStream(mockOutputStream);
       socketFactory.expects(once()).method("createSocket").will(returnValue(mockSocket));
       
       assertTrue ("message not sent",tcpLink.sendDataMessage(new byte32, 32));
       
       assertTrue("output stream write not called",mockOutputStream.writeCalled);
       assertTrue("output stream close not called",mockOutputStream.closeCalled);
       assertTrue("socket close not called",mockSocket.closeCalled);
   }
   
   public void testSendMessageSocketException() {
   
       MockOutputStream mockOutputStream = new MockOutputStream();
       MockSocket mockSocket = new MockSocket();
       mockSocket.setOutputStream(mockOutputStream);
       mockSocket.sendException = true;
       socketFactory.expects(once()).method("createSocket").will(returnValue(mockSocket));
       
       assertFalse ("message should be not sent",tcpLink.sendDataMessage(new byte32,32));
       
   }

Cette solution est bien entendu applicable lorsque l'on a peu de cas possibles ou de méthodes à tester, dans le cas contraire le nombre de combinaisons possibles deviendrait assez élevé et la complexité de l'implémentation des objets factices rendrait les tests difficile à écrire et donc non exempts de défaut.

Il y a d'autres pistes à creuser, comme l'utilisation d'une classe de délégation ou l'utilisation d'une classe dérivée implémentant une interface similaire à la classe à simuler. Peut être à un prochain billet pour décrire ces deux méthodes.

lundi 11 décembre 2006

Xplanner et Mylar

Première étape d'intégration d'Xplanner et de Mylar en utilisant le connecteur web generique

dimanche 10 décembre 2006

Matt l'écrivain

Matt Raible s'est laché ce week-end, beaucoup d'articles avec un contenu très dense.

  • TSE Keynote: The Bigger Picture with Adrian Colyer, Un tour d'horizon des sous projets de Spring : SFW, SWF, SWS, S-OSGi. Les services d'entreprise clustering, persistence, messaging et scheduling. Les tendances du moment: SAO, Web 2.0/RIA, RAD stacks. Je rajouterai à tout çà le projet de validation.
  • Spring-OSGI with Adrian Colyer

Beaucoup de choses à lire et à faire ....http://www.thespringexperience.com/

jeudi 7 décembre 2006

Mylar

Affaire à suivre, téléchargé le plugin aujourd'hui j'essaye d'intégrer avec Xplanner dés que j'ai le temps. [1] Mylar is a task focused UI for Eclipse that makes working with very large workspaces as easy as working with small ones. It makes tasks a first class part of Eclipse, and integrates task repositories such as Bugzilla, Trac, and JIRA.

Mylar est une interface orienté tâches pour Eclipse, qui permet de travailler avec des workspaces très grand ou très petit.... Il peut intégrer des gestionnaires de tâches comme Bugzilla, Trac ou JIRA.

En plus on peut facilement ? intégerer des outils 'web based'. Si j'ai bien compris, je travaille sur un tâche (correction de défaut, ajout, modification), lorsque j'ai fini la tâche, j'ai ouvert un tas de chose sur mon "bureau Eclipse". Si je désire revenir sur la tâche, mon environnement se repositionne automatiquement dans la même configuration que lorsque j'ai quitté la tâche en question.

  • Un témoignage d'un utilisateur content ici

Notes

[1] j'abrége un peu car je viens de perdre le précédent billet que j'était en train d'éditer

Spring C3P0 hibernate

Modifs du 07-Dec-2006

Après quelques heures de galére de de googleage, je livre tout cru le fichier de configuration Spring C3p0 Hibernate qui fonctionne avec MySQL et qui tiens la distance en espérant que cela pourra servir à quelqu'un.

La première config n'était pas si bonne que cela, ci-dessous les corrections à apporter ... (Ce coup ci j'ai vérifié !). Voir tout en bas de la doc C3p0 le paragraphe sur la configuration avec hibernate.

Reste encore à régler le problème du cache.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
    <bean id="c3p0dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="${jdbc.driverClassName}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="acquireIncrement" value="1"></property>
        <property name="minPoolSize" value="5"></property>
        <property name="maxPoolSize" value="50"></property>
        <property name="acquireRetryAttempts" value="10"></property>
        <property name="preferredTestQuery" value="SELECT 1"></property> 
        <property name="testConnectionOnCheckin" value="true"></property>
        <property name="testConnectionOnCheckout" value="false"></property>
        <property name="maxIdleTime" value="50"></property>
        <property name="idleConnectionTestPeriod" value="30"></property>
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="c3p0dataSource"/>
        <property name="mappingResources">
            <list>
                   ................................... 	
	   </list>
        </property>
        <property name="hibernateProperties">
            <props>
               <prop key="hibernate.dialect">${hibernate.dialect}</prop>
               <prop key="hibernate.hbm2ddl.auto">update</prop>
		<prop key="hibernate.connection.release_mode">on_close</prop>
               <prop key="hibernate.c3p0.acquire_increment">1</prop>
               <prop key="hibernate.c3p0.minPoolSize">2</prop>
               <prop key="hibernate.c3p0.maxPoolSize">50</prop>
		<prop key="hibernate.c3p0.timeout">1800</prop>
		<prop key="hibernate.c3p0.max_statement">0</prop>  
		<prop key="hibernate.c3p0.idle_test_period">30</prop>          
	     </props>
        </property>
    </bean>
</beans>

lundi 4 décembre 2006

RMI maven et tomcat

Il y a peu de temps je mettais en oeuvre une librairie utilisant RMI. Tout avait l'air de fonctionner impeccable lors du lancement de l'applicaton dans Eclipse à l'aide du plugin jetty.

Lors du déploiment dans tomcat, quelle ne fut pas ma surprise de voir l'application se planter en affichant des RMI Exceptions du plus bel effet.

Un coup de google et je tombe sur plusieurs articles / billets / archives email ou il est conseillé d'installer tomcat dans un répertoire sans espace, ce qui n'est bien entendu pas le cas par défaut sous Windows. Aussitôt lu aussitôt fait cela régle effectivement le problème.

Dans le même temps je suis en train de mettre en oeuvre des tests automatiques d'integration sous maven et quelle ne fut pas surprise de rencontrer le même problème. Ces tests tournant sans problème sous eclipse je pensais que mon paramètrage du fichier pom.xml était en cause ....

Mais non, même problème, même solution, il suffit de redéfinir dans le fichier settings.xml de maven un repository local situé dans un répertoire sans espace

Moralité, je me suis toujours méfié des caractères exotiques dans les noms de fichiers, et bien j'avais raison.

vendredi 1 décembre 2006

Nouveau nouveau

Et voila, mise en route d'un blog de plus, celui-ci centré sur les problématiques développement d'application et autre bidouillage. Affaire à suivre...

Pour l'instant pas d'habillage et je vais récupérer les billets techniques qui n'ont rien à faire dans l'autre blog

Quelques idées de billet :

  • maven et les tests d'intégration
  • Démarrage de projet
  • Développer à l'envers
  • Retrour d'expérience sur un an de pratique TDD

Communication - Simplicité - Feedback - Courage - Respect

Je crois vraiment que dans ces 5 valeurs d'XP, la plus difficile à mettre en oeuvre (à posséder ?) est bien la communication.

Calendrier

décembre 2006 »
lunmarmerjeuvensamdim
123
45678910
11121314151617
18192021222324
25262728293031