Mocks SOAPUI mutualisés : tu pousses le bouchon un peu trop loin…

Lors de la mise en place de projets de type SOA, vous rencontrerez souvent la même problématique : comment dérouler mes processus sans me heurter à tous les problèmes des services dont je dépends ?! Lors d’un développement incluant de nombreux acteurs, cela peut être source d’énervement, de démotivation…
La solution : les bouchons (ou mocks pour les anglicistes).
Cette méthode est très utilisée dans les tests unitaires, mais souvent sous-estimée, voire inexistante, dans le cadre de projets transverses. Cependant elle peut vous simplifier la vie et transformer un parcours du combattant en ballade de santé… enfin peut être pas quand même…
Il existe différentes méthodes pour les réaliser : mise en place de TestSuite, de services spécifiques dans un BUS (OSB par exemple) ou via le très utile SOAPUI, entre autres. C’est à cette dernière méthode qu’ira ma préférence, celle-ci ayant l’avantage d’être gratuite (on préfèrera OSB par exemple s’il est à disposition) et de partager les bouchons en place.
Cependant lors d’une mise en place « industrielle » de mocks, SOAPUI présente quelques limites qui peuvent être gênantes… quelles sont ces limites ? Comment les contourner ? Poussons un peu plus …

SOAPUI et les « MockService »

SOAPUI est un véritable couteau suisse pour tout ce qui touche au développement de web-services. Ici, on n’abordera qu’un point particulier : la mise en place de bouchons, et plus particulièrement leur industrialisation.
Par industrialisation, il faut comprendre ici la mise en place de bouchons sur un serveur commun (développement par exemple) tout en permettant une administration simple et des évolutions rapides.
Les MockService sont des éléments d’un projet SOAPUI qui vont permettre de mettre en place la simulation d’une, ou plusieurs, des opérations mises à disposition par un service.
Sans s’attarder dans le détail de la réalisation de MockService, il faut savoir que SOAPUI propose la mise en place de scripts Groovy pour la détermination (voire sa génération) d’une réponse en fonction d’un appel. Avec une telle souplesse, on pourra s’adapter à presque toutes les attentes.

Les problèmes…

SOAPUI propose un script pour lancer un MockService (mockRunner). Au début cela fonctionne très bien, puis on arrive très rapidement au cœur du problème qui a motivé cet article :

  • Pour chaque MockService, il faut lancer une instance de script
    • La première conséquence : chaque MockService est lancé dans une JVM lui étant propre. De ce fait, impossible de réutiliser le même port pour lancer 2 MockServices !
    • La seconde : chaque JVM est consommatrice de ressources, chose qui peut être cependant « limitée » par de bons paramétrages …
  • La commande permettant de lancer la commande sans « blocage » (paramètre –b) a un comportement étrange… elle lance bien le MockService … mais se termine dès que le chargement est terminé ! Bref on perd l’intérêt du mode silencieux. Ce point ressemble à un bogue, qui sera peut être corrigé dans une prochaine version.

Souvent  une fois ces points observés, on ne cherche plus à mutualiser les MockServices…
Note : il existe également une autre méthode consistant à déployer les MockService sur un serveur de servlet sous la forme de WAR. Pour le moment je n’ai pas testé cette méthode. Peut être dans un prochain article !
 

La solution !

Pourtant, me direz-vous, le lancement de plusieurs MockServices fonctionne très bien lorsque l’on est dans l’interface de SOAPUI ?
Eh bien oui, et c’est justement sur cette assertion que se base cette solution : mettre en place un petit utilitaire qui utilisera directement les APIs de SOAPUI pour lancer autant de MockService que l’on souhaite. Le tout dans une JVM et sur le même port.

Le principe

  • Une configuration via Spring (v3.0.5)
    Principalement pour simplifier la configuration / les injections de dépendances
  • Une liste de « Mock »
    Paramétrée dans la configuration Spring, pour fournir une méthode simple de mise à jour
  • Un « MockServiceRunner »
    Qui a en charge de lancer nos MockService, utilisant les APIs SOAPUI et la liste de Mock pour mettre à disposition les bouchons nécessaires
  • Un SOAPUI positionné sur le serveur (v3.6.1)
    Notamment pour avoir à disposition ses librairies

Le détail

Tout d’abord, quelques dépendances sont nécessaires au projet pour réaliser cet utilitaire :

  • Spring 3.0.5
  • Bibliothèques présentes dans SOAPUI (v3.6.1, jar du dossier lib/bin)

Voici la configuration Spring utilisée, avec principalement :

  • Un bean MockServiceRunner
  • Un bean par Mock à prendre en compte par le MockServiceRunner,
    Simples POJOs permettant de fournir les informations nécessaires à la récupération du MockService et à sa configuration.
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd                          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">
       <!-- properties to define default host/port and project path -->
       <context:property-placeholder location="classpath:mock.properties"/>
       <!-- MockServiceRunner -->
       <bean id="mockServiceRunner" class="com.sandbox.mock.MockServiceRunner" >
             <property name="mocks">
                   <ref bean="mockList" />
             </property>
       </bean>
       <!-- Mock List which will be launched by MockServiceRunner -->
       <util:list id="mockList" list-class="java.util.ArrayList">
         <ref bean="Mock1"/>
         <ref bean="Mock2"/>
       </util:list>
       <!-- Definition of mocks which could be added to bean mockList -->
       <bean name="Mock1" class="com.sandbox.mock.Mock">
         <property name="name" value="NomMockService1" />
         <property name="project" value="${projectHome}/Mock1-soapui-project.xml" />
         <property name="host" value="${defaultHost}" />
         <property name="port" value="${defaultPort}" />
         <property name="endpoint" value="" />
       </bean>
       <bean name="Mock2" class="com.sandbox.mock.Mock">
         <property name="name" value="NomMockService2" />
         <property name="project" value="${projectHome}/Mock2-soapui-project.xml" />
         <property name="host" value="localhost" />
         <property name="port" value="8080" />
         <property name="endpoint" value="/customEndpointMock2" />
       </bean>
</beans>

Le code représentatif du MockServiceRunner :

      /**
       * Launch all Mock configured in spring configuration
       *
       * @throws XmlException
       * @throws IOException
       * @throws SoapUIException
       */       public void runMocks() throws XmlException, IOException, SoapUIException { 
            // Creating WSDLMockServices from configured mock list
            List<WsdlMockService> mocksWSDL = new ArrayList<WsdlMockService>();
            for (Mock mock : mocks) {
                  WsdlMockService wms = getWsdlMockService(mock);
                  mocksWSDL.add(wms);
        }
            // initiate mock service runner
        SoapUIMockServiceRunner runner = new  SoapUIMockServiceRunner();
        // launch all mock services
        for (WsdlMockService mockWSDL :  mocksWSDL) {
                  launchWsdlMockService(runner, mockWSDL);
            }
      }
      /**
       *
       * Initiate a WsdlMockService from a simple Mock from spring configuration
       *
       * @param mock
       * @return
       * @throws XmlException
       * @throws IOException
       * @throws SoapUIException
       */
      private WsdlMockService getWsdlMockService(Mock mock) throws XmlException, IOException, SoapUIException {
            WsdlMockService wms = new WsdlProject(mock.getProject()).getMockServiceByName(mock.getName());
            wms.setHost(mock.getHost());
            wms.setPort(mock.getPort());
            if (!mock.getEndpoint().equals("")) {
                  wms.setPath(mock.getEndpoint());
            }
            return wms;
      }
      /**
       * Launch a WsdlMockService
       *
       * @param runner
       * @param wsm
       */
      private void launchWsdlMockService(SoapUIMockServiceRunner runner, WsdlMockService wsm) {
            log.info("Traitement Mock : " + wsm.getName());
        runner.setBlock(false);
        runner.setSaveAfterRun(false);
        runner.setEnableUI(false);
        log.info(wsm.getName() + " :");
        log.info("  starting... with :");
        log.info("  - host     : " + wsm.getHost());
        log.info("  - port     : " + wsm.getPort());
        log.info("  - path     : " + wsm.getPath());
        log.info("  - URL      : http://"+wsm.getHost()+":"+wsm.getPort()+wsm.getPath()+"?WSDL");
        runner.runMockService(wsm);
        log.info("  started !");
      }

Il faut retenir principalement la méthode runMocks qui va orchestrer le démarrage des Mocks : récupération de la configuration, construction des objets « SOAPUI » et enfin lancement.

L’installation

Pour mettre en œuvre ce MockServiceRunner, le plus simple est de :

  • Copier les projets contenant les MockService SOAPUI dans un dossier sur le serveur cible
  • Copier l’outil SOAPUI sur le serveur cible
  • Copier les bibliothèques Spring et autre dépendances dans un dossier
  • Copier les classes / ressources  du batch dans un dossier
  • Mettre en place des commandes pour démarrer/arrêter (et autres pouvant être utiles)

Exemple de script de démarrage sur Linux

export CLASSPATH=".:./spring/*:../soapui-3.6.1/lib/*:../soapui-3.6.1/bin/soapui-3.6.1.jar"
nohup java com.sandbox.mock.MockServiceRunner > mockservicerunner.log 2>&1 &

Exemple de script de d’arrêt sur Linux

processId=`ps -ef | grep java | grep com.sandbox.mock.MockServiceRunner | awk '{print $2;}'`
kill $processId

Conclusion : a-t-on trop poussé le bouchon ?

Au final, si on fait un petit bilan, on a obtenu :

  • Des bouchons à disposition sur un serveur commun : mutualisation des tests
  • Des commandes pour administrer (start/stop/…) : administration simple
  • Une configuration externalisée des projets via la configuration : mise en place de la configuration simple, gestion de la liste de Mocks par simple configuration XML
  • Un SOAPUI à disposition sur le serveur, potentiellement utilisable pour modifier les projets de Mocks de manière graphique (redirection X11) : évolution  rapide et simple des comportements des Mocks

Tous les objectifs qu’on souhaitait obtenir ont donc été respectés. Avec ces informations, il vous sera possible de mettre en place un tel environnement, en l’adaptant si nécessaire à vos besoins. Reste à sauter le pas (ou le bouchon… au choix) !
Liens : site SOAPUI

1 réflexion sur “Mocks SOAPUI mutualisés : tu pousses le bouchon un peu trop loin…”

  1. Ping : SoapUI : scripting et automatisation de test - EASYTEAM

Les commentaires sont fermés.