2011-08-23 7 views
11

Sto cercando di capire come implementare il test di integrazione multi-bundle in OSGi usando JUnit.Utilizzo di servizi dichiarativi OSGi nel contesto di un test JUnit

Con test di integrazione, intendo istanziare un sottoinsieme dei pacchetti per convalidare automaticamente la funzionalità in tale sottosistema.

Stiamo eseguendo Equinox e utilizzando Eclipse come toolchain. Eclipse offre l'opzione "Esegui come plug-in JUnit" che porta in su il framework OSGi e crea un'istanza dei bundle di configurazione, quindi suppongo che questo sia il percorso da seguire, ma non trovo il modo di inserire riferimenti DS nei miei test. Ho visto l'uso di ServiceTracker come un mezzo programmatico per accedere ai diversi pacchetti di servizi, ma che batte lo scopo di avere DS, non è vero?

Sono appena iniziato con OSGI, quindi immagino che mi manchi solo un pezzo del puzzle che mi permetterebbe di mettere insieme i miei test multi-bundle.

Qualche idea?

Grazie, Gerard.

* EDIT: SOLUZIONE *

Dopo aver guardato ulteriormente in questo problema, ho finalmente capito come mettere questo mult-fascio test di integrazione in atto utilizzando il JUnit plug-in funzione:

Per l'iniezione di servizi dinamici per funzionare, occorre creare un file di definizione del servizio in cui devono essere dichiarate le dipendenze iniettate, come di solito si fa quando si lavora con DS. Questo file va (in genere) nella directory OSGI-INF/. per esempio. OSGI-INF/service.xml

service.xml deve dichiarare le dipendenze richieste per questo test, ma non offrire un servizio a sé stante:

service.xml 
<?xml version="1.0" encoding="UTF-8"?> 
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="MyTest" activate="startup" deactivate="shutdown"> 

    <implementation class="com.test.functionaltest.MyTester"/> 
    <reference name="OtherService" interface="com.product.service.FooService" policy="static" cardinality="1..1" bind="onServiceUp" unbind="onServiceDown"/> 

</scr:component> 

Questo indicherà DS di iniettare la dipendenza FooService utilizzando il metodo dichiarato onServiceUp. onServiceDown deve essere implementato come viene chiamato durante la fase di spegnimento di OSGi dopo l'esecuzione dei test.

com.test.functionaltest.MyTester contiene i metodi di prova da eseguire, seguendo le pratiche tipiche di JUnit.

Fino a qui, è tutto 'dal libro'. Tuttavia, se viene eseguito Junit, genererà NullPointerException quando accede a un riferimento a FooService. La ragione è che il framework OSGi è in una condizione di competizione con il contesto runner di test di JUnit e, solitamente, il corridore di prova Junit vince quella gara, eseguendo i test prima che il riferimento al servizio richiesto venga iniettato.

Per risolvere questa situazione, è necessario che il test Junit attenda che il runtime di OSGi esegua il proprio lavoro. Ho risolto questo problema utilizzando CountDownLatch, che è inizializzato sul numero di servizi dipendenti richiesti nel test. Quindi tutti i metodi di iniezione delle dipendenze eseguono il conto alla rovescia e quando sono tutti terminati, il test inizierà. Il codice è simile al seguente:

private static CountDownLatch dependencyLatch = new CountDownLatch(1);// 1 = number of dependencies required  
static FooService fooService = null; 
public void onFooServiceUp(FooService service) { 
    fooService = service; 
    dependencyLatch.countDown(); 
} 

Nota che il riferimento fooService deve essere statico per consentire la condivisione il riferimento al servizio tra l'OSGi ed i contesti di esecuzione JUnit. CountDownLatch fornisce un meccanismo di sincronizzazione di alto livello per la pubblicazione sicura di questo riferimento condiviso.

Poi, un controllo delle dipendenze deve essere aggiunto prima della esecuzione del test:

@Before 
public void dependencyCheck() { 
    // Wait for OSGi dependencies 
    try { 
     dependencyLatch.await(10, TimeUnit.SECONDS); 
     // Dependencies fulfilled 
    } catch (InterruptedException ex) { 
     fail("OSGi dependencies unfulfilled"); 
    } 
} 

In questo modo il quadro Junit attende il servizio OSGi DS per iniettare le dipendenze o non dopo il timeout.

Mi ci è voluto un po 'di tempo per capire completamente questo. Spero che in futuro risparmi un po 'di mal di testa nei confronti degli altri programmatori.

risposta

6

* EDIT: SOLUZIONE *

Dopo aver guardato ulteriormente in questo problema, ho finalmente capito come mettere questo mult-fascio test di integrazione in atto utilizzando il JUnit plug-in funzione:

Per l'iniezione di servizi dinamici per funzionare, occorre creare un file di definizione del servizio in cui devono essere dichiarate le dipendenze iniettate, come di solito si fa quando si lavora con DS. Questo file va (in genere) nella directory OSGI-INF/. per esempio. OSGI-INF/service.xml

service.xml deve dichiarare le dipendenze richieste per questo test, ma non offrire un servizio a sé stante:

service.xml 
<?xml version="1.0" encoding="UTF-8"?> 
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="MyTest" activate="startup" deactivate="shutdown"> 

    <implementation class="com.test.functionaltest.MyTester"/> 
    <reference name="OtherService" interface="com.product.service.FooService" policy="static" cardinality="1..1" bind="onServiceUp" unbind="onServiceDown"/> 

</scr:component> 

Questo indicherà DS di iniettare la dipendenza FooService utilizzando il metodo dichiarato onServiceUp. onServiceDown deve essere implementato come viene chiamato durante la fase di spegnimento di OSGi dopo l'esecuzione dei test.

com.test.functionaltest.MyTester contiene i metodi di prova da eseguire, seguendo le pratiche tipiche di JUnit.

Fino a qui, è tutto 'dal libro'. Tuttavia, se viene eseguito Junit, genererà NullPointerException quando accede a un riferimento a FooService.La ragione è che il framework OSGi è in una condizione di competizione con il contesto runner di test di JUnit e, solitamente, il corridore di prova Junit vince quella gara, eseguendo i test prima che il riferimento al servizio richiesto venga iniettato.

Per risolvere questa situazione, è necessario che il test Junit attenda che il runtime di OSGi esegua il proprio lavoro. Ho risolto questo problema utilizzando CountDownLatch, che è inizializzato sul numero di servizi dipendenti richiesti nel test. Quindi tutti i metodi di iniezione delle dipendenze eseguono il conto alla rovescia e quando sono tutti terminati, il test inizierà. Il codice è simile al seguente:

private static CountDownLatch dependencyLatch = new CountDownLatch(1);// 1 = number of dependencies required  
static FooService fooService = null; 
public void onFooServiceUp(FooService service) { 
    fooService = service; 
    dependencyLatch.countDown(); 
} 

Nota che il riferimento fooService deve essere statico per consentire la condivisione il riferimento al servizio tra l'OSGi ed i contesti di esecuzione JUnit. CountDownLatch fornisce un meccanismo di sincronizzazione di alto livello per la pubblicazione sicura di questo riferimento condiviso.

Poi, un controllo delle dipendenze deve essere aggiunto prima della esecuzione del test:

@Before 
public void dependencyCheck() { 
    // Wait for OSGi dependencies 
    try { 
     dependencyLatch.await(10, TimeUnit.SECONDS); 
     // Dependencies fulfilled 
    } catch (InterruptedException ex) { 
     fail("OSGi dependencies unfulfilled"); 
    } 
} 

In questo modo il quadro Junit attende il servizio OSGi DS per iniettare le dipendenze o non dopo il timeout.

Mi ci è voluto un po 'di tempo per capire completamente questo. Spero che in futuro risparmi un po 'di mal di testa nei confronti degli altri programmatori.

1

Non ho familiarità con gli strumenti Eclipse menzionati, ma abbiamo utilizzato con successo Pax Exam per i test di integrazione in Apache Sling. Se hai familiarità con Maven, il POM a https://svn.apache.org/repos/asf/sling/trunk/installer/it/pom.xml potrebbe aiutarti a iniziare, e https://github.com/tonit/Learn-PaxExam sembra un buon punto di partenza.

Lo Sling testing tools può anche aiutare in questo contesto consentendo ai bundle di fornire i test JUnit a un framework OSGi in fase di runtime, utile se il progetto genera un jar eseguibile che può essere utilizzato per il test.

1

Si imposta utilizzando le schede sulla configurazione di esecuzione.

Quindi fare clic con il pulsante destro del mouse, selezionare "Esegui come", selezionare "Esegui configurazioni ...", fare doppio clic su "Test plug-in JUnit", quindi aggiungere le dipendenze nella scheda plug-in - praticamente lo stesso del normale avvio

Alcuni link: http://publib.boulder.ibm.com/infocenter/ratdevz/v8r0/index.jsp?topic=/org.eclipse.pde.doc.user/guide/tools/launchers/junit_launcher.htm e http://publib.boulder.ibm.com/infocenter/ratdevz/v8r0/index.jsp?topic=/org.eclipse.pde.doc.user/guide/tools/launchers/junit_main.htm

+0

Certo, questa è l'opzione "Esegui come plug-in JUnit" che ho menzionato, ma non sembra essere sufficiente per ottenere i Servizi dichiarativi iniettati. cioè dove dichiari i metodi di legame? C'è anche la domanda sul contesto. L'iniezione avverrà nel contesto di Junit? Se è così, come funziona quel meccanismo? – maasg

+0

Lo stesso che usi DS nell'applicazione. È necessario aggiungere il pacchetto DS, questo esegue la scansione di altri bundle mentre vengono caricati e gestisce il cablaggio. I binding di DS sono dichiarati nel componente xml in OSGI-INF nel jar del bundle. Vedi http://www.vogella.de/articles/OSGi/article.html#declarativeservices_run per una spiegazione sull'uso di DS nei progetti Plugin di Eclipse. HTH – earcam

+0

Grazie per il suggerimento. I metodi di iniezione di DS non vengono richiamati nel contesto dell'esecuzione del plug-in di Junit. Il mio OSG-INF li dichiara correttamente. – maasg

1

credo che sarebbe stato un po 'più pulito per entrare in possesso del org.apache.felix.scr.ScrService e attendere attivamente per la componente a diventare attivo. Questa interfaccia è implementata sia da equinox che da felix.

Java doc e API Usage.

-2

Penso che nella soluzione di cui sopra il CountDownLatch non sia necessario.

Il problema è che JUnit in DS Context istanzia una classe JUnitTest ciascuno per conto suo. Il primo contesto DS crea un'istanza della classe JUnitTest e chiama l'associazione su FooServiceUp per FooService, ma dopo questo JUnit crea un'istanza della sua classe JUnitTest senza chiamare il metodo di associazione su FooServiceUp. In questo caso, FooService si trova in JUnitTest non disponibile.

Se dichiari FooService come statico (come hai fatto) e assegni nel metodo onFooServiceUp non hai bisogno della costruzione con CountDownLatch.