Testování JMS v unit testech

Dnes jsem strávil skoro celý den tím, že jsem potřeboval vyřešit následující problém. Mám aplikaci, která poměrně sofistikovaně využívá několik JMS front pro asynchronní zpracování požadavků z klientských aplikací. Celé je to postavené nad serverem Weblogic a aplikace využívá frameworku Spring. Další komplikací je, že veškeré fronty jsou v aplikačním kontextu získávány z JNDI, včetně konektivity do databáze. Na následujících řádcích zkusím popsat, jak jsem se s tímto problémem popasoval. Budu rád za jakékoliv reakce, co jsem měl udělat lépe a co jsem udělal špatně.

Abych se vyhnul polemice, co je a co není unit testování a jestli nemám špatně navrženou aplikaci, když musím řešit JMS, JNDI apod., pak vězte, že implementaci jsem nepsal a implementaci jsem nesměl refaktorovat, protože se jednalo o celkem zásadní funkcionalitu, která se nachází těsně před ostrým nasazením a můj projektový vedoucí se obával toho, že kdybych provedl nějaký větší refactoring, mohlo by to způsobit v této fázi projektu více škody nežli užitku.

Ale i přesto bylo potřeba do této implementace doimplementovat několik požadavků. Rozhodl jsem se nejprve stávající implementaci pokrýt testy tak, abych tím, co tam doplním nerozbil to, co již funguje. A zde jsem trochu narazil. Implementace tohoto modulu je velice těsně spjata s JMS implementací Weblogicu a navíc tak, že se jedná o několik front, které jsou vzájemně propojeny a zpráva zkrze ně postupně “propadává” podle definovaných pravidel.

Abych to celé byl tedy schopen spustit jako unit test, musel jsem vyřešit pár “drobností”, a to poskytovatele JNDI kontextu, transaction manager a vlastní implementaci JMS. Pro JNDI kontextu jsem jednoduše využil prostředků Springu. Pro transakční manager jsem využil Bitronix Transaction Manager a pro messagingové služby padlo rozhodnutí na Apache ActiveMQ.

JNDI kontext

Vytvoření vlastního JNDI kontextu je ve Springu triviální úloha. Já jsem si pro to napsal jednoduchou utilitku, která mi ji ještě více zjednodušuje:

package eu.kratochvil.utils.spring;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;

import java.util.HashMap;
import java.util.Map;

/**
 * Simple implementation of JNDI context that should be used in unit-tests
 * within Spring context.
 * 

* Basic usage is simple: * * <bean id="jndiContext" class="cz.isvs.reg.ros.ws.repository.util.MockSpringJndiContext"> * <property name="jndiContext"> * <map> * <entry key="ros.VstupniFronta" value-ref="destinationVstupniFronta"/> * <entry key="ros.VystupniFronta" value-ref="destinationVystupniFronta"/> * <entry key="weblogic.jms.XAConnectionFactory" value-ref="activeMQConnectionFactory"/> * </map> * </property> * </bean> * * There isn't needet to use something like jndi.properties because jndi context is define * directly inside spring context. * * @author Jiri Kratochvil (jiri.kratochvil@jetminds.com) */ public class MockSpringJndiContext implements InitializingBean { public static final Logger logger = LoggerFactory.getLogger(MockSpringJndiContext.class); Map jndiContext = new HashMap(); public void setJndiContext(Map jndiContext) { this.jndiContext = jndiContext; } @Override public void afterPropertiesSet() throws Exception { logger.info("Initializing JNDI context"); SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder(); for (Map.Entry entry : jndiContext.entrySet()) { logger.debug("Binding {} in JNDI context", entry.getKey().toString()); builder.bind(entry.getKey().toString(), entry.getValue()); } } }

Použití je pak naprosto triviální:

    
        
            
                
                
                
            
        
    

Inicializací tohoto springového beanu se vytvoří JNDI kontext, který obsahuje dvě JMS fronty a ještě connection factory, o které budu mluvit dále. Pokud použijete Spring není potřeba dále definovat jak se k JNDI kontextu připojit, což se běžně dělá pomocí property souboru jndi.properties umístěném na classpath.

JMS server

Dále tedy potřebujeme vytvořit instanci embedovaného JMS serveru. Pro mé účely jsem zvolil Apache ActiveMQ, který jde snadno inicializovat pomocí Springu, kde to vypadá následovně:

   
    
        
            
        
    

    
    
        
    

    
    
    

    
    

    
    
        
        
        
    

Popis co a proč je použito v předchozím listingu jsou přímo v něm, tedy ve stručnosti. Vytvořili jsme si instanci message brokeru, který nám nahrazuje vlastní JMS server v aplikačním serveru, následně jsme zadefinovali jednotlivé fronty, které jsou použity ve vlastní implementaci business logiky a konečně connection factory, která nám umožní napojení vlastních listenerů JMS front a zároveň přes ni budeme posílat testovací zprávy z našit unit testů.

Nyní bych se ještě vrátil k tomu, proč jsem do JNDI kontextu umístil weblogic.jms.XAConnectionFactory. Důvod je takový, že aplikace, které využívají JMS ve Weblogiku by měli referenci na connection factory získávat právě z JNDI.

Transaction Manager

Poslední věc, kterou musíme vyřešit bez aplikačního serveru je transaction manager – tedy “něco”, co nám bude řídit vlastní transakce. Na to jsem použil Bitronix Transaction Manager což je celkem mocná implementace transakčního manažeru. Pro naše účely bude použití triviální:

      
    
        
        
    

    
    

    
        
        
        
    

Závěrem

Kouzlo tohoto použití je v tom, že nyní stačí spustit unit test, který inicializuje springový kontext a zároveň načte definici kontextu vlastní aplikace, která právě využívá JNDI pro získání referencí na fronty a vše by mělo fungovat stejně, jako kdybychom tuto implementaci měli deployovanou přímo na aplikačním serveru. Pro úplnost dodávám ukázku definice kontextu přímo v aplikaci:

    
    
    
    

    
    

    
    
        
        
    

    

    

    
        
        
    

    

Na celém tomto cvičení je pěkné to, že jednoduchou springovou konfigurací jsme schopni rozchodit určitou část aplikace, která za normálních okolností nelze bez aplikačního serveru spustit, a my si tak můžeme napsat testy, které lze snadno spouštět bez neustálého a otravného redeploymentu na aplikační server.

Leave a Reply

Your email address will not be published. Required fields are marked *