Tutoriel pour mettre en place et exécuter les tests unitaires sur un projet multibundle déployé sous Karaf et développé sous Eclipse

Tests unitaires sur un projet multibundle sous Eclipse et déployé sous Karaf

Olivier ROZIER

Dans notre tutoriel « Tutoriel pour développer et déployer un service Web RESTFUL OSGI multibundle sous Eclipse et Karaf », nous avons étudié la manière de créer un projet multibundle destiné à être déployé sous Karaf et développé sous Eclipse.

Nous avons également vu les différentes méthodes de déploiement d'une telle application en poussant la problématique jusqu'à créer une distribution personnalisée de Karaf.

Aujourd'hui, par le présent tutoriel, nous allons parler de la manière d'exécuter les tests unitaires sur un tel projet en reprenant le code source du tutoriel ci-dessus évoqué.

Avant de commencer à lire ce tutoriel, nous vous conseillons donc de lire le tutoriel « Tutoriel pour développer et déployer un service Web RESTFUL OSGI multibundle sous Eclipse et Karaf » afin de comprendre, en détail, la manière dont est articulé un tel projet.

Pour réaliser les tests unitaires, nous utiliserons le framework « Pax Exam » tout en réutilisant la distribution personnalisée de Karaf que nous avons générée auparavant.

Après avoir lu notre précédent tutoriel puis celui-ci, vous constaterez la facilité, sous Eclipse :

- à créer un service web REST composé de plusieurs modules ;

- à les intégrer sous Karaf ;

- à réaliser les tests unitaires sur un tel projet.

Les deux derniers points pouvant être réalisés à l'issue d'une seule et même compilation.

Pour réagir au contenu de ce tutoriel, un espace de dialogue vous est proposé sur le forum : Commentez

- Titre : Tutoriel pour mettre en place et exécuter les tests unitaires sur un projet multibundle déployé sous Karaf et développé sous Eclipse

- Auteur : Olivier ROZIER

- Parution : 9 septembre 2016

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2016 Olivier ROZIER. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Configuration logicielle

- II. OSGI et les tests unitaires

- III. Création du module de test

- IV. Configuration de Pax Exam

- IV-A. Mise en place de la stratégie de test

- IV-B. Configuration du container de test

- IV-C. Configuration des méthodes de test

- V. Lancement des tests

- V-A. Cas où certains modules du projet ont été modifiés

- V-B. Cas où seul le module « module-test » a été modifié

- VI. Code source de l'exemple

- VII. Conclusion

- Index

I - Configuration logicielle

Les logiciels utilisés dans ce tutoriel sont les suivants :

- Eclipse Mars ;

- Karaf 4.0.4 ;

- JDK 1.7.

Les API Java utilisées sont :

- JPA 2 ;

- JAXB.

Les frameworks utilisés :

- Hibernate ;

- Apache CXF ;

- Pax Exam 4.7.0.

II - OSGI et les tests unitaires

Comme pour toute application, vous pouvez réaliser des tests unitaires sur les bundles OSGI que nous venons de développer.

Ces tests unitaires sont facilités par l'utilisation du framework « Pax Exam » dont vous pouvez consulter la page officielle à cette adresse : https://ops4j1.jira.com/wiki/display/PAXEXAM4/Pax+Exam

Les avantages de « Pax Exam » sont nombreux et se résument à ceci :

- contrôler facilement le framework OSGI, le framework de test (JUnit dans notre cas, mais aussi TestNG) et les bundles ;

- démarrer un serveur Karaf pour y exécuter des commandes, déployer des features… ;

- exécuter facilement des tests OSGI par ajout d'annotations spéciales et d'une méthode de configuration à une classe Junit.

« Pax Exam » se compose des éléments suivants :

- un « Test Driver » qui lance le framework OSGI et les bundles à tester. Le rôle de cet élément est de construire des bundles à la volée à partir de nos cas de tests et de les injecter dans le container (Karaf) ;

- un « Test Container » composé de deux parties :

- un container natif (« Native Container ») qui lance le framework OSGI embarqué à Karaf dans la machine virtuelle du « test driver » ;

- un « Forked Container » qui permet d'exécuter les bundles dans une machine virtuelle séparée et contrôlée par le « Test Driver ».

Le « Test Container » supporte différentes stratégies pour redémarrer ou réutiliser le framework OSGI en cours d'exécution pour chaque test.

III - Création du module de test

1. Cliquez droit sur le projet « customerRestFulHibernateWS » → « New » → « Project ». Dans la fenêtre qui apparaît, saisissez « maven » dans le filtre puis choisissez « Maven module ».

Cliquez sur le bouton « Next ».

2. Dans la fenêtre qui apparaît, ne cochez pas la case « Create a simple project (skip archetype selection) ».
Saisissez le nom de notre module de test. Par exemple : « module-test ».

Cliquez sur le bouton « Next ».

3. Dans le champ « Filter » de la fenêtre qui apparaît, saisissez « pax » puis, dans la liste qui apparaît, choisissez la ligne correspondant à « exam-karaf-archetype » puis cliquez sur le bouton « Next ».

4. Renseignez la fenêtre qui apparaît de cette manière :
Group Id : com.exemple.customerRestFulHibernateWS
Artefact Id : module-test
Version : 1.0.0
Package : com.exemple.customerRestFulHibernateWS.test

Cliquez sur le bouton « Finish ».

5. Dans l'explorateur de projets d'Eclipse, nous pouvons voir que notre projet « customerRestFulHibernateWS » a été complété par le module « module-test » :

Le fichier « pom.xml » a été modifié pour y inclure le nouveau module (« module-test ») :

On peut également voir qu'un nouveau projet nommé « module-test » a été créé :

6. Renommez le fichier « /src/test/java/com/exemple/customerRestFulHibernateWS/test/MyTest.java » en « /src/test/java/com/exemple/customerRestFulHibernateWS/test/CustomerDaoTest.java » en cliquant droit dessus puis en sélectionnant « Rename… ».

7. Renommez également la classe à l'intérieur de ce fichier si ce n'est pas fait automatiquement. Le nom de la classe passera de MyTest à CustomerDaoTestcomme le présente la capture d'écran ci-dessous :

Il s'agira de notre classe de test pour la classe CustomerDao.

IV - Configuration de Pax Exam

Dans le projet « module-test », ouvrez le programme « /src/test/java/com/exemple/customerRestFulHibernateWS/test/CustomerDaoTest.java ».

Nous pouvons lire les lignes suivantes :

module-test/MyTest.java

1. package com.exemple.customerRestFulHibernateWS.test;

3. import static org.hamcrest.CoreMatchers.is;

4. import static org.hamcrest.CoreMatchers.notNullValue;

5. import static org.junit.Assert.assertThat;

6. import static org.ops4j.pax.exam.CoreOptions.maven;

7. import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.configureConsole;

8. import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.features;

9. import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.karafDistributionConfiguration;

10. import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.keepRuntimeFolder;

11. import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.logLevel;

13. import java.io.File;

15. import javax.inject.Inject;

17. import org.apache.karaf.features.BootFinished;

18. import org.apache.karaf.features.FeaturesService;

19. import org.junit.Test;

20. import org.junit.runner.RunWith;

21. import org.ops4j.pax.exam.Configuration;

22. import org.ops4j.pax.exam.Option;

23. import org.ops4j.pax.exam.junit.PaxExam;

24. import org.ops4j.pax.exam.karaf.options.LogLevelOption.LogLevel;

25. import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;

26. import org.ops4j.pax.exam.spi.reactors.PerClass;

27. import org.osgi.framework.BundleContext;

29. @RunWith(PaxExam.class)  //Permet de lier Pax Exam a JUnit afin de mettre en place un container de test (Karaf) contenant un framework OSGI et nos bundles a tester

30. @ExamReactorStrategy(PerClass.class)  //Determine si le framework OSGI doit redemarrer ou pas pour chaque test.

31. public class CustomerDaoTest {

33. @Inject

34. private BundleContext bc;

37. @Inject

38. protected FeaturesService featuresService;

40. /**

41. * To make sure the tests run only when the boot features are fully

42. * installed

43. */

44. @Inject

45. BootFinished bootFinished;

47. @Configuration

48. public static Option[] configuration() throws Exception

49. {

50. return new Option[]

51. {

52. karafDistributionConfiguration().frameworkUrl(maven().groupId("org.apache.karaf").artifactId("apache-karaf")

53. .type("zip").version("3.0.3"))

54. .unpackDirectory(new File("target/paxexam/unpack/"))

55. .useDeployFolder(false),

56. configureConsole().ignoreLocalConsole(),

57. logLevel(LogLevel.INFO),

58. keepRuntimeFolder(),

59. features(

60. maven().groupId("org.apache.karaf.features")

61. .artifactId("standard").type("xml")

62. .classifier("features").version("3.0.3"),

63. "eventadmin")

64. };

65. }

67. @Test

68. public void shouldHaveBundleContext()

69. {

70. assertThat(bc, is(notNullValue()));

71. }

73. @Test

74. public void checkEventFeature() throws Exception

75. {

76. assertThat(featuresService.isInstalled(featuresService.getFeature("eventadmin")), is(true));

77. }

79. }

Il s'agit du code Java créé par défaut lors de la création de notre sous-module. Ce code permet d'installer un container Karaf et de le configurer.

Or, au cours de nos tests unitaires, nous aurons certainement besoin de créer plusieurs classes de test et par conséquent, le code permettant d'installer et de paramétrer notre serveur Karaf sera réutilisé.

Il est par conséquent judicieux de le placer dans une classe abstraite afin d'éviter d'écrire plusieurs fois le même code et de centraliser la version du container en cas d'évolution. Pour cela :

1. Créez une nouvelle classe nommée KarafContainer en cliquant droit sur le package « com.exemple.customerRestFulHibernateWS.test » puis, dans le menu contextuel qui apparaît, choisissez « New » → «  Class » :

2. Dans la section « Name », saisissez « KarafContainer » et cochez la case « abstract » :

Cliquez sur « Finish ». Le fichier « KarafContainer.java » s'ouvre alors.

3. Coupez-collez le contenu de la méthode configuration()dans la classe KarafContainer. Copiez également les annotations « @RunWith » et « @ExamReactorStrategy ». La classe KarafContainer ressemble donc à cela :

module-test/KarafContainer

1. package com.exemple.customerRestFulHibernateWS.test;

3. import static org.hamcrest.CoreMatchers.is;

4. import static org.hamcrest.CoreMatchers.notNullValue;

5. import static org.junit.Assert.assertThat;

6. import static org.ops4j.pax.exam.CoreOptions.maven;

7. import static org.ops4j.pax.exam.CoreOptions.mavenBundle;

8. import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.configureConsole;

9. import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.features;

10. import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.karafDistributionConfiguration;

11. import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.keepRuntimeFolder;

12. import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.logLevel;

14. import java.io.File;

16. import javax.inject.Inject;

18. import org.apache.karaf.features.BootFinished;

19. import org.apache.karaf.features.FeaturesService;

20. import org.junit.Test;

21. import org.ops4j.pax.exam.Configuration;

22. import org.ops4j.pax.exam.Option;

23. import org.ops4j.pax.exam.karaf.options.LogLevelOption.LogLevel;

24. import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;

25. import org.ops4j.pax.exam.spi.reactors.PerMethod;

26. import org.osgi.framework.BundleContext;

27. @RunWith(PaxExam.class)  //Permet de lier Pax Exam a JUnit afin de mettre en place un container de test (Karaf) contenant un framework OSGI et nos bundles a tester

28. @ExamReactorStrategy(PerClass.class)  //Determine si le framework OSGI doit redemarrer ou pas pour chaque test.

29. public abstract class KarafContainer

30. {

31. @Configuration

32. public static Option[] configuration() throws Exception

33. {

34. return new Option[]

35. {

36. karafDistributionConfiguration().frameworkUrl(maven().groupId("org.apache.karaf").artifactId("apache-karaf")

37. .type("zip").version("3.0.3"))

38. .unpackDirectory(new File("target/paxexam/unpack/"))

39. .useDeployFolder(false),

40. configureConsole().ignoreLocalConsole(),

41. logLevel(LogLevel.INFO),

42. keepRuntimeFolder(),

43. features(

44. maven().groupId("org.apache.karaf.features")

45. .artifactId("standard").type("xml")

46. .classifier("features").version("3.0.3"),

47. "eventadmin")

48. };

49. }

50. }

Dans les paragraphes suivants, nous présenterons comment configurer « Pax Exam » et par conséquent comment modifier ces lignes de manière à ce que ce framework réponde parfaitement à vos besoins.

IV-A - Mise en place de la stratégie de test

En premier lieu, nous remarquons, lignes 29 et 30 du fichier « /src/test/java/com/exemple/customerRestFulHibernateWS/test/KarafContainer.java », les annotations « @RunWith » et « @ExamReactorStrategy ».

L'annotation @RunWith(PaxExam.class)permet de lier le framework « Pax Exam » à JUnit pour que « Pax Exam » installe, par la suite, un container de test (dans notre cas, il s'agit de Karaf) avec un framework OSGI contenant nos bundles de test.

L'annotation @ExamReactorStrategy(PerClass.class)détermine si le framework OSGI doit redémarrer ou pas après chaque test. Elle détermine donc la stratégie de réaction de « Pax Exam ».

Comme vous pouvez le constater, dans notre cas, la stratégie utilisée est « PerClass ». Les stratégies possibles sont les suivantes :

- PerMethod

Avec cette stratégie, le container de test (Karaf) est démarré avant chaque méthode de test individuel et arrêté juste après.

Il s'agit de la stratégie par défaut (si la stratégie n'est pas renseignée) ;

- PerClass

Avec cette stratégie, le container de test est démarré et arrêté avant et après que tous les tests sur une classe de test donnée sont terminés. Par conséquent, les méthodes de tests suivantes ne sont pas complètement isolées des effets de bord des méthodes précédemment exécutées sur la même classe de test ;

- PerSuite
Avec cette stratégie, le container de test est démarré une seule fois avant l'exécution de toutes les classes de test et arrêté après que tous les tests ont été effectués. Cette stratégie minimise le temps de démarrage et d'arrêt du container au détriment de l'isolation et des effets de bord engendrés par les classes de tests ou les méthodes.

Remarquez que, dans notre classe KarafContainer, c'est la stratégie « PerClass » qui est renseignée par défaut lors de la création de la classe. Cette stratégie nous convient étant donné les tests unitaires que nous ferons du fait que très peu d'effets de bord sont à redouter entre les méthodes. Il faudra néanmoins accorder une attention particulière au fait que tout enregistrement inséré dans une table via une méthode de test devra être supprimé à la fin de l'exécution de cette méthode.

IV-B - Configuration du container de test

Tout test unitaire réalisé par « Pax Exam » requiert une méthode utilisant l'annotation « @configuration » et retournant un tableau d'options de configuration. Ces options définissent l'ensemble des bundles utilisés par le framework OSGI dans le container de test.

Dans notre cas, cette méthode se situe dans la classe KarafContainer et est la suivante :

Options de configuration

1. @Configuration

2. public static Option[] configuration() throws Exception

3. {

4. return new Option[]

5. {

6. karafDistributionConfiguration().frameworkUrl(maven().groupId("org.apache.karaf").artifactId("apache-karaf")

7. .type("zip").version("3.0.3"))

8. .unpackDirectory(new File("target/paxexam/unpack/"))

9. .useDeployFolder(false),

10. configureConsole().ignoreLocalConsole(),

11. logLevel(LogLevel.INFO),

12. keepRuntimeFolder(),

13. features(

14. maven().groupId("org.apache.karaf.features")

15. .artifactId("standard").type("xml")

16. .classifier("features").version("3.0.3"),

17. "eventadmin")

18. };

19. }

Nous remarquons en premier, de la ligne 52 à la ligne 53 la configuration de l'installation de Karaf.

Ces lignes permettent en effet au framework « Pax Exam » d'installer le container de test. Nous constatons que, par défaut, il installera la version « 3.0.3 » de Karaf.

Étant donné que nous avons choisi Karaf en version « 4.0.4 enterprise » lors de la réalisation de notre projet, nous vous suggérons de modifier cette version « 3.0.3 » en « 4.0.4 enterprise ».

Ainsi, le code devient le suivant :

1. karafDistributionConfiguration().frameworkUrl(maven().groupId("org.apache.karaf").artifactId("apache-karaf")

2. .type("zip").version("4.0.4"))

3. .unpackDirectory(new File("target/paxexam/unpack/"))

4. .useDeployFolder(false),

5. configureConsole().ignoreLocalConsole(),

6. logLevel(LogLevel.INFO),

7. keepRuntimeFolder(),

8. features(

9. maven().groupId("org.apache.karaf.features")

10. .artifactId("enterprise").type("xml")

11. .classifier("features").version("4.0.4"),

12. "eventadmin")

En plus des lignes concernant la configuration de notre container de test, nous pouvons ajouter plusieurs options de configuration possibles. Parmi elles, notons les plus importantes nous concernant :

- features()
Permet de fournir au container les features à installer. Il s'agit en fait des features que nous avons renseignées dans le fichier « module-kar/feature.xml » du tutoriel « Tutoriel pour développer et déployer un service Web RESTFUL OSGI multibundle sous Eclipse et Karaf » ;

- replaceConfigurationFile()
Permet de fournir ou de remplacer un fichier de configuration dans le répertoire « /etc » de Karaf. Cette option est utile, comme nous le verrons plus loin, pour installer notre DataSource ;

- editConfigurationFilePut()
Permet de remplacer ou d'ajouter une option dans un fichier de configuration de Karaf. Cette option de configuration nous permettra de renseigner notre repository local Maven afin que Karaf aille récupérer les bundles constituant notre service web dans le repository local, là où ils ont été déposés par Eclipse ;

- mavenBundle()
Permet de préciser l'URL Maven d'un bundle placé dans un repository local ou distant. Nous nous en servirons, par exemple, pour récupérer le driver PostgreSql nécessaire à notre application ;

- junitBundles()
Permet de fournir au container une version par défaut de JUnit et de ses dépendances en tant que bundles OSGI.

La liste des options de configuration est disponible à cette adresse : https://ops4j1.jira.com/wiki/display/PAXEXAM4/Configuration+Options#ConfigurationOptions-OptionOverview

Afin de prévoir la mise en place de la Data Source Factory et de la Data Source, copiez le fichier « src/main/resources/etc/org.ops4j.datasource-customerDb.cfg » disponible dans le sous-module « module-karaf-custom-distribution » dans le répertoire « src/test/resources/ » de notre sous-module « module-test ».

À l'aide de ces informations, nous pouvons compléter la méthode configuration() de notre classe KarafContainer de la manière suivante :

module-test/KarafContainer.java

1. @Configuration

2. public static Option[] configuration() throws Exception

3. {

4. return new Option[]

5. {

6. karafDistributionConfiguration().frameworkUrl(maven().groupId("org.apache.karaf").artifactId("apache-karaf")

7. .type("zip").version("4.0.4"))

8. .unpackDirectory(new File("target/paxexam/unpack/"))

9. .useDeployFolder(false),

10. configureConsole().ignoreLocalConsole(),

11. logLevel(LogLevel.INFO),

12. keepRuntimeFolder(),

14. //Installation des features preinstallees dans Karaf

15. features(maven().groupId("org.apache.karaf.features").artifactId("enterprise").type("xml").classifier("features").version("4.0.4"),"jdbc"),

16. features(maven().groupId("org.apache.karaf.features").artifactId("enterprise").type("xml").classifier("features").version("4.0.4"),"jpa"),

17. features(maven().groupId("org.apache.karaf.features").artifactId("enterprise").type("xml").classifier("features").version("4.0.4"),"transaction"),

18. features(maven().groupId("org.apache.karaf.features").artifactId("enterprise").type("xml").classifier("features").version("4.0.4"),"jndi"),

20. //Installation des features supplementaires

21. features(maven().groupId("org.apache.cxf.karaf").artifactId("apache-cxf").type("xml").classifier("features").version("3.1.5"),"cxf"),

22. features(maven().groupId("org.apache.karaf.features").artifactId("enterprise").type("xml").classifier("features").version("4.0.4"),"pax-jdbc"),

23. features(maven().groupId("org.apache.karaf.features").artifactId("enterprise").type("xml").classifier("features").version("4.0.4"),"pax-jdbc-config"),

24. features(maven().groupId("org.apache.karaf.features").artifactId("enterprise").type("xml").classifier("features").version("4.0.4"),"pax-jdbc-pool-dbcp2"),

25. features(maven().groupId("org.hibernate").artifactId("hibernate-osgi").type("xml").classifier("karaf").version("5.1.0.Final"),"hibernate-orm"),

27. //Configuration du fichier "etc/org.ops4j.pax.url.mvn.cfg" pour renseigner notre repository local (derniere ligne)

28. editConfigurationFilePut( "etc/org.ops4j.pax.url.mvn.cfg", "org.ops4j.pax.url.mvn.repositories", "http://repo1.maven.org/maven2@id=central, "

29. + "http://repository.springsource.com/maven/bundles/release@id=spring.ebr.release, "

30. + "http://repository.springsource.com/maven/bundles/external@id=spring.ebr.external, "

31. + "http://zodiac.springsource.com/maven/bundles/release@id=gemini, "

32. + "http://repository.apache.org/content/groups/snapshots-group@id=apache@snapshots@noreleases, "

33. + "https://oss.sonatype.org/content/repositories/snapshots@id=sonatype.snapshots.deploy@snapshots@noreleases, "

34. + "https://oss.sonatype.org/content/repositories/ops4j-snapshots@id=ops4j.sonatype.snapshots.deploy@snapshots@noreleases, "

35. + "http://repository.springsource.com/maven/bundles/external@id=spring-ebr-repository@snapshots@noreleases, "

36. + "file://C:/Users/orozier/.m2/repository@id=1@snapshots"),

38. //Installation du driver PostgreSql

39. mavenBundle().groupId("org.postgresql").artifactId("postgresql").version("9.4-1203-jdbc41"),

41. //Creation de la Datasource

42. replaceConfigurationFile("etc/org.ops4j.datasource-customerDb.cfg", new File("src/test/resources/org.ops4j.datasource-customerDb.cfg")),

44. //Installation des bundles JUnit

45. junitBundles(),

47. //Installation de nos bundles

48. mavenBundle().groupId("com.exemple.customerRestFulHibernateWS").artifactId("module-data").version("1.0.0"),

49. mavenBundle().groupId("com.exemple.customerRestFulHibernateWS").artifactId("module-services").version("1.0.0"),

50. mavenBundle().groupId("com.exemple.customerRestFulHibernateWS").artifactId("module-jaxrs").version("1.0.0")

51. };

52. }

Il est intéressant de savoir qu'en utilisant « Pax Exam » pour générer vos tests unitaires, vous pouvez non seulement importer une distribution officielle de Karaf en guise de container (comme nous l'avons vu ci-dessus), mais vous pouvez également importer une distribution personnalisée comme celle que nous avons réalisée dans notre tutoriel « Tutoriel pour développer et déployer un service Web RESTFUL OSGI multibundle sous Eclipse et Karaf ». C'est d'ailleurs sur les sources de ce projet que nous basons le présent tutoriel.

Par conséquent, autant utiliser notre version personnalisée pour effectuer les tests unitaires plutôt qu'une version de container dans laquelle il nous faudrait tout installer (comme ci-dessus), tout en ayant des doutes sur la similitude entre cette nouvelle plateforme et notre distribution personnalisée.

Dès lors, vous voyez que, dans un même projet, il est possible de :

- organiser votre projet dans Eclipse en modules représentatifs des couches applicatives comme nous l'avons fait dans le tutoriel « Tutoriel pour développer et déployer un service Web RESTFUL OSGI multibundle sous Eclipse et Karaf » ;

- transformer ces modules en autant de bundles OSGI ;

- générer une distribution personnalisée de Karaf embarquant, en natif, les bundles développés ;

- utiliser cette version personnalisée pour effectuer nos tests unitaires.

L'ensemble de ces tâches peut se faire que par un seul build (ce qui ne vous dispense pas de faire des tests unitaires au fur et à mesure de la création de vos classes).

L'avantage majeur est que, à l'issue du développement, non seulement les tests unitaires sont effectués, mais ils le sont sur une distribution de Karaf dans laquelle tous les développements sont déjà intégrés.

La phase d'intégration des bundles dans Karaf (avec tout ce que cela implique : installation, tests…) n'est donc plus du tout chronophage puisqu'elle est réalisée durant la phase de développement.

Pour utiliser notre distribution personnalisée de Karaf pour effectuer les tests unitaires, il vous suffit de remplacer le code de la méthode  configuration() vu ci-dessus, par celui-ci :

module-test/KarafContainer.java

1. @Configuration

2. public static Option[] configuration() throws Exception

3. {

4. File customKarafDistribution = new File ("C:/Users/orozier/workspaceEclipse/customerRestFulHibernateWS/module-karaf-custom-distribution/target/module-karaf-custom-distribution-1.0.0.zip");

6. return new Option[]

7. {

8. karafDistributionConfiguration().frameworkUrl(customKarafDistribution.toURI().toString())

9. .name("Karaf custom distrib")

10. .unpackDirectory(new File("target/paxexam/unpack/"))

11. .useDeployFolder(false),

12. configureConsole().ignoreLocalConsole(),

13. logLevel(LogLevel.INFO),

14. keepRuntimeFolder(),

16. //Installation des bundles JUnit

17. junitBundles()

18. };

19. }

Vous n'avez plus à utiliser le fichier « src/main/resources/etc/org.ops4j.datasource-customerDb.cfg » permettant de configurer la source de données.

IV-C - Configuration des méthodes de test

Toute méthode utilisant l'annotation « @Test » sera exécutée dans le framework OSGI lancé par le container de test (dans notre cas : Karaf).

Face à une telle méthode, « Pax Exam » construit un bundle à la volée, appelé « test prob », qui contient la classe de test et installe ce bundle dans Karaf.

En utilisant les attributs de la classe de test annotés par « @Inject », le corps des méthodes de test peut accéder au bundle context du « test prob » ou à n'importe quel service obtenu depuis le registre des services OSGI.

L'annotation « @Inject » permet d'injecter des dépendances dans la classe de test.

L'annotation « @Filter » peut être ajoutée pour restreindre l'ensemble des services. Cette annotation possède un argument optionnel nommé « timeout » qui spécifie la durée, en millisecondes, durant laquelle la présence du service est testée.

Dans le paragraphe précédent, nous avons installé :

- une Datasource ;

- les features nécessaires à nos projets.

Dès lors, il est intéressant de créer des méthodes de test, dans la classe abstraite « KarafContainer », qui permettent de tester la bonne installation des features et le bon fonctionnement de la Datasource.

Méthode de test de la classe KarafContainer

À partir de ce que nous venons d'expliquer, nous pouvons donc compléter notre classe abstraite de la manière suivante :

module-test/KarafContainer.java

1. package com.exemple.customerRestFulHibernateWS.test;

3. import static org.ops4j.pax.exam.CoreOptions.junitBundles;

4. import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.configureConsole;

5. import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.karafDistributionConfiguration;

6. import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.keepRuntimeFolder;

7. import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.logLevel;

9. import java.io.File;

11. import javax.inject.Inject;

13. import org.apache.karaf.features.BootFinished;

14. import org.junit.Test;

15. import org.junit.runner.RunWith;

16. import org.ops4j.pax.exam.Configuration;

17. import org.ops4j.pax.exam.Option;

18. import org.ops4j.pax.exam.junit.PaxExam;

19. import org.ops4j.pax.exam.karaf.options.LogLevelOption.LogLevel;

20. import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;

21. import org.ops4j.pax.exam.spi.reactors.PerClass;

23. import javax.sql.DataSource;

24. import java.sql.Connection;

25. import java.sql.ResultSet;

26. import java.sql.SQLException;

27. import java.sql.Statement;

29. import static org.hamcrest.CoreMatchers.is;

30. import static org.hamcrest.CoreMatchers.notNullValue;

31. import static org.junit.Assert.assertEquals;

32. import static org.junit.Assert.assertThat;

33. import static org.junit.Assert.assertTrue;

35. import org.ops4j.pax.exam.util.Filter;

36. import org.osgi.framework.BundleContext;

37. import org.apache.karaf.features.FeaturesService;

41. @RunWith(PaxExam.class)  //Permet de lier Pax Exam a JUnit afin de mettre en place un container de test (Karaf) contenant un framework OSGI et nos bundles a tester

42. @ExamReactorStrategy(PerClass.class)  //Determine si le framework OSGI doit redemarrer ou pas pour chaque test.

43. public abstract class KarafContainer

44. {

45. @Inject //Annotation permettant d'injecter des dependances dans la classe de test

46. @Filter("(osgi.jndi.service.name=customerDb)") //L'annotation Filter peut etre ajoutee pour restreindre l'ensemble des services. Cette annotation possede un argument optionnel nomme "timeout" qui specifie la duree, en millisecondes, durant laquelle la presence du service est testee

47. DataSource dataSource;

49. //Declaration d'un attribut de type BundleContext qui recupere le context du "test prob"

50. @Inject

51. private BundleContext bc;

53. //Declaration d'un attribut de type FeaturesService afin de verifier la bonne installation des features

54. @Inject

55. protected FeaturesService featuresService;

57. //Declaration d'un attribut de type BootFinished pour etre sur que les tests se font uniquement si les features de demarrage sont toutes installees

58. @Inject

59. BootFinished bootFinished;

61. //Declaration des versions definies dans le fichier module-kar/feature.xml

62. public static final String VERSION_HIBERNATE_ORM = "5.1.0.Final";

63. public static final String VERSION_CXF = "3.1.5";

64. public static final String VERSION_PAX_JDBC = "0.7.0";

65. public static final String VERSION_JPA = "2.3.0";

66. public static final String VERSION_TRANSACTION = "1.3.0";

67. public static final String VERSION_JNDI = "4.0.4";

69. @Configuration

70. public static Option[] configuration() throws Exception

71. {

72. File customKarafDistribution = new File ("C:/Users/orozier/workspaceEclipse/customerRestFulHibernateWS/module-karaf-custom-distribution/target/module-karaf-custom-distribution-1.0.0.zip");

74. return new Option[]

75. {

76. karafDistributionConfiguration().frameworkUrl(customKarafDistribution.toURI().toString())

77. .name("Karaf custom distrib")

78. .unpackDirectory(new File("target/paxexam/unpack/"))

79. .useDeployFolder(false),

80. configureConsole().ignoreLocalConsole(),

81. logLevel(LogLevel.INFO),

82. keepRuntimeFolder(),

84. //Installation des bundles JUnit

85. junitBundles()

87. /*karafDistributionConfiguration().frameworkUrl(maven().groupId("org.apache.karaf").artifactId("apache-karaf")

88. .type("zip").version("4.0.4"))

89. .unpackDirectory(new File("target/paxexam/unpack/"))

90. .useDeployFolder(false),

91. configureConsole().ignoreLocalConsole(),

92. logLevel(LogLevel.INFO),

93. keepRuntimeFolder(),

95. //Installation des features preinstallees dans Karaf

96. features(maven().groupId("org.apache.karaf.features").artifactId("enterprise").type("xml").classifier("features").version("4.0.4"),"jdbc"),

97. features(maven().groupId("org.apache.karaf.features").artifactId("enterprise").type("xml").classifier("features").version("4.0.4"),"jpa"),

98. features(maven().groupId("org.apache.karaf.features").artifactId("enterprise").type("xml").classifier("features").version("4.0.4"),"transaction"),

99. features(maven().groupId("org.apache.karaf.features").artifactId("enterprise").type("xml").classifier("features").version("4.0.4"),"jndi"),

101. //Installation des features supplementaires

102. features(maven().groupId("org.apache.cxf.karaf").artifactId("apache-cxf").type("xml").classifier("features").version("3.1.5"),"cxf"),

103. features(maven().groupId("org.apache.karaf.features").artifactId("enterprise").type("xml").classifier("features").version("4.0.4"),"pax-jdbc"),

104. features(maven().groupId("org.apache.karaf.features").artifactId("enterprise").type("xml").classifier("features").version("4.0.4"),"pax-jdbc-config"),

105. features(maven().groupId("org.apache.karaf.features").artifactId("enterprise").type("xml").classifier("features").version("4.0.4"),"pax-jdbc-pool-dbcp2"),

106. features(maven().groupId("org.hibernate").artifactId("hibernate-osgi").type("xml").classifier("karaf").version("5.1.0.Final"),"hibernate-orm"),

108. editConfigurationFilePut("etc/org.ops4j.pax.web.cfg", "org.osgi.service.http.port", "alternative-port"),

110. //Configuration du fichier "etc/org.ops4j.pax.url.mvn.cfg" pour renseigner notre repository local (derniere ligne)

111. editConfigurationFilePut( "etc/org.ops4j.pax.url.mvn.cfg", "org.ops4j.pax.url.mvn.repositories", "http://repo1.maven.org/maven2@id=central, "

112. + "http://repository.springsource.com/maven/bundles/release@id=spring.ebr.release, "

113. + "http://repository.springsource.com/maven/bundles/external@id=spring.ebr.external, "

114. + "http://zodiac.springsource.com/maven/bundles/release@id=gemini, "

115. + "http://repository.apache.org/content/groups/snapshots-group@id=apache@snapshots@noreleases, "

116. + "https://oss.sonatype.org/content/repositories/snapshots@id=sonatype.snapshots.deploy@snapshots@noreleases, "

117. + "https://oss.sonatype.org/content/repositories/ops4j-snapshots@id=ops4j.sonatype.snapshots.deploy@snapshots@noreleases, "

118. + "http://repository.springsource.com/maven/bundles/external@id=spring-ebr-repository@snapshots@noreleases, "

119. + "file://C:/Users/orozier/.m2/repository@id=1@snapshots"),

121. //Installation du driver PostgreSql

122. mavenBundle().groupId("org.postgresql").artifactId("postgresql").version("9.4-1203-jdbc41"),

124. //Creation de la Datasource

125. replaceConfigurationFile("etc/org.ops4j.datasource-customerDb.cfg", new File("src/test/resources/org.ops4j.datasource-customerDb.cfg")),

127. //Installation des bundles JUnit

128. junitBundles(),

130. //Installation de nos bundles

131. mavenBundle().groupId("com.exemple.customerRestFulHibernateWS").artifactId("module-data").version("1.0.0"),

132. mavenBundle().groupId("com.exemple.customerRestFulHibernateWS").artifactId("module-services").version("1.0.0"),

133. mavenBundle().groupId("com.exemple.customerRestFulHibernateWS").artifactId("module-jaxrs").version("1.0.0")*/

134. };

135. }

137. /**

138. * Teste l'existence du Bundle context du "test prob"

139. */

140. @Test

141. public void shouldHaveBundleContext()

142. {

143. assertThat(bc, is(notNullValue()));

144. }

147. /**

148. * Teste la bonne installation de la feature jdbc

149. * @throws Exception

150. */

151. @Test

152. public void verifJdbcFeature() throws Exception

153. {

154. //On ne verifie pas la version car quelle que soit cette version, la feature jdbc embarquee dans Karaf permettra toujours de saisir des requetes sur la base de donnees via la console Karaf

155. assertThat(featuresService.isInstalled(featuresService.getFeature("jdbc")), is(true));

156. }

158. /**

159. * Teste la bonne installation de la feature jpa

160. * @throws Exception

161. */

162. @Test

163. public void verifJpaFeature() throws Exception

164. {

165. assertTrue(featuresService.isInstalled(featuresService.getFeature("jpa", VERSION_JPA)));

166. }

169. /**

170. * Teste la bonne installation de la feature transaction

171. * @throws Exception

172. */

173. @Test

174. public void verifTransactionFeature() throws Exception

175. {

176. assertTrue(featuresService.isInstalled(featuresService.getFeature("transaction", VERSION_TRANSACTION)));

177. }

179. /**

180. * Teste la bonne installation de la feature jndi

181. * @throws Exception

182. */

183. @Test

184. public void verifJndiFeature() throws Exception

185. {

186. assertTrue(featuresService.isInstalled(featuresService.getFeature("jndi", VERSION_JNDI)));

187. }

190. /**

191. * Teste la bonne installation de la feature cxf

192. * @throws Exception

193. */

194. @Test

195. public void verifCxfFeature() throws Exception

196. {

197. assertTrue(featuresService.isInstalled(featuresService.getFeature("cxf", VERSION_CXF)));

198. }

201. /**

202. * Teste la bonne installation de la feature pax-jdbc

203. * @throws Exception

204. */

205. @Test

206. public void verifPaxJdbcFeature() throws Exception

207. {

208. assertTrue(featuresService.isInstalled(featuresService.getFeature("pax-jdbc", VERSION_PAX_JDBC)));

209. }

212. /**

213. * Teste la bonne installation de la feature pax-jdbc-config

214. * @throws Exception

215. */

216. @Test

217. public void verifPaxJdbcConfigFeature() throws Exception

218. {

219. assertTrue(featuresService.isInstalled(featuresService.getFeature("pax-jdbc-config", VERSION_PAX_JDBC)));

220. }

222. /**

223. * Teste la bonne installation de la feature pax-jdbc-pool-dbcp2

224. * @throws Exception

225. */

226. @Test

227. public void verifPaxJdbcPoolDbcp2Feature() throws Exception

228. {

229. assertTrue(featuresService.isInstalled(featuresService.getFeature("pax-jdbc-pool-dbcp2", VERSION_PAX_JDBC)));

230. }

233. /**

234. * Teste la bonne installation de la feature hibernate-orm

235. * @throws Exception

236. */

237. @Test

238. public void verifHibernateOrmFeature() throws Exception

239. {

240. assertTrue(featuresService.isInstalled(featuresService.getFeature("hibernate-orm", VERSION_HIBERNATE_ORM)));

241. }

244. /**

245. * Teste l'accessibilite de la base de donnees

246. * @throws SQLException

247. */

248. @Test

249. public void testDataSourceFromConfig() throws SQLException

250. {

251. Connection connection = dataSource.getConnection();

252. Statement statement = connection.createStatement();

253. statement.execute("CREATE TABLE ctm.t1 (col1 INTEGER NOT NULL, col2 CHAR(25), PRIMARY KEY (COL1)) ");

254. statement.executeUpdate("insert into ctm.t1 (col1, col2) values(101, 'test JUnit')");

255. ResultSet result = statement.executeQuery("select col1 from ctm.t1 where col2 = 'test JUnit'");

257. while (result.next())

258. {

259. assertEquals(101, result.getInt("col1"));

260. }

261. result.close();

263. statement.execute("DROP TABLE ctm.T1");

265. statement.close();

266. connection.close();

267. }

268. }

Comme vous pouvez le voir, nous avons ajouté les attributs de type :

- « DataSource » qui récupère, via l'annotation « @Filter », la Datasource installée sous forme de service dans Karaf. Cet attribut est ensuite utilisé par la méthode de test « testDataSourceFromConfig() » ;

- « BundleContext » qui récupère le contexte du « test prob » afin de vérifier que « Pax Exam » a bien créé un bundle à la volée concernant notre classe de test ;

- « FeaturesService » qui permet de vérifier la bonne installation des features dans Karaf ;

- « BootFinished » qui permet d'être sûr que les tests seront réalisés seulement si les features de démarrage ont été installées.

La méthode « testDataSourceFromConfig() » constitue un test unitaire dont le but est de vérifier l'accessibilité de la base de données. Pour ce faire, cette méthode crée une table dans la base de données, y insère un enregistrement, le sélectionne puis supprime la table.

Les méthodes « checkXxxxxxxxFeature() » permettent de tester la bonne installation de chacune des features installées dans Karaf.

Méthode de test de la classe CustomerDaoTest

Il est désormais indispensable que notre classe CustomerDaoTest hérite de notre classe abstraite KarafContaineravant d'y ajouter des méthodes de test faisant référence à la classe CustomerDaoImpl. La classe CustomerDaoTest ressemblera donc à ceci :

module-test/CustomerDaoTest.java

1. package com.exemple.customerRestFulHibernateWS.test;

4. public class CustomerDaoTest extends KarafContainer

5. {

7. }

Cette classe contiendra des références à l'interface CustomerDao qui appartient au module « module-data ». Il est donc nécessaire d'adapter le fichier « module-test/pom.xml » en y ajoutant :

- la dépendance à l'artefact « module-data » :

module-test/pom.xml

1. <dependency>

2. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

3. <artifactId>module-data</artifactId>

4. <version>[1.0.0,2.0)</version>

5. <type>bundle</type>

6. </dependency>

- Le plugin « maven-bundle-plugin » afin d'importer le package « com.exemple.customerRestFulHibernateWS.data.CustomerDao » :

module-test/pom.xml

1. <plugin>

2. <groupId>org.apache.felix</groupId>

3. <artifactId>maven-bundle-plugin</artifactId>

4. <version>3.0.0</version>

5. <extensions>true</extensions>

6. <configuration>

7. <instructions>

8. <!-- Le fichier MANIFEST.MF sera rempli avec les informations ci-dessous. Pour plus d'informations : http://wiki.osgi.org/wiki/Category:Manifest_Header -->

10. <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName> <!-- Nom qui identifie le bundle de manière unique -->

11. <Bundle-Version>${project.version}</Bundle-Version><!-- Decrit la version du bundle et permet l'activation simultanee de plusieurs versions d'un bundle dans la meme instance de structure -->

12. <Import-Package>*</Import-Package>

13. </instructions>

14. </configuration>

15. </plugin>

La classe CustomerDaoTest aura pour rôle de tester les méthodes de la classe abstraite CustomerDao contenue dans le module « module-data ». Pour cette raison, dans la classe CustomerDaoTest, nous allons déclarer un attribut de type CustomerDao que nous allons annoter à l'aide de l'annotation « @Inject ». Cette déclaration aura pour effet d'injecter un service « CustomerDao » dans notre container dans le but de tester ses méthodes.

Dans ce tutoriel, concernant la classe CustomerDaonous nous limiterons à réaliser les tests unitaires sur la méthode findCustomer(final long customerId). Il va de soi que, dans vos projets, il sera nécessaire de réaliser les tests unitaires sur toutes les méthodes de vos classes.

Ainsi, notre classe CustomerDaoTest ressemblera à ceci :

module-test/CustomerDaoTest

1. package com.exemple.customerRestFulHibernateWS.test;

3. import static org.junit.Assert.assertEquals;

4. import static org.junit.Assert.assertNull;

6. import java.sql.Connection;

7. import java.sql.ResultSet;

8. import java.sql.SQLException;

9. import java.sql.Statement;

11. import javax.inject.Inject;

13. import org.junit.Test;

15. import com.exemple.customerRestFulHibernateWS.data.CustomerDao;

17. public class CustomerDaoTest extends KarafContainer

18. {

19. @Inject

20. private CustomerDao customerDao;

22. /**

23. * Teste la methode findCustomer(final long customerId) de la classe CustomerDaoImpl

24. * @throws SQLException

25. */

26. @Test

27. public void testFindCustomer() throws SQLException

28. {

29. Connection connection = dataSource.getConnection();

30. Statement statement = connection.createStatement();

31. ResultSet result = null;

33. try

34. {

35. statement.executeUpdate("insert into ctm.customer (prenom, nom) values('Eddy', 'Sion')");

36. result = statement.executeQuery("select idcustomer, prenom, nom from ctm.customer where nom = 'Sion'");

39. while (result.next())

40. {

41. //Verification que l'enregistrement retourne est celui escompte

42. assertEquals("Customer[idCustomer=" + result.getLong("idcustomer") + ", nom=Sion, prenom=Eddy]", customerDao.findCustomer(result.getLong("idcustomer")).toString());

44. //Verification que la methode retourne null en cas d'enregistrement non trouve

45. assertNull(customerDao.findCustomer(0));

46. }

47. }

48. catch(Exception e)

49. {

50. e.printStackTrace();

51. }

52. finally

53. {

54. result.close();

56. statement.executeUpdate("delete from ctm.customer where nom = 'Sion'");

58. statement.close();

59. connection.close();

60. }

61. }

62. }

Méthode de test de la classe CustomerServiceTest

1. Complétez le fichier « module-test/pom.xml » pour ajouter la dépendance concernant le module « module-services » :

module-test/pom.xml

1. <dependencies>

2. <dependency>

3. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

4. <artifactId>module-data</artifactId>

5. <version>[1.0.0,2.0)</version>

6. <type>bundle</type>

7. </dependency>

9. <dependency>

10. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

11. <artifactId>module-services</artifactId>

12. <version>[1.0.0,2.0)</version>

13. <type>bundle</type>

14. </dependency>

2. La classe CustomerServiceTest peut désormais être créée dans le module « module-test » et plus précisément dans le package « com.exemple.customerRestFulHibernateWS.test » de la manière suivante :

module-test/CustomerServiceTest.java

1. package com.exemple.customerRestFulHibernateWS.test;

3. import static org.junit.Assert.assertEquals;

4. import static org.junit.Assert.assertNull;

6. import java.sql.Connection;

7. import java.sql.ResultSet;

8. import java.sql.SQLException;

9. import java.sql.Statement;

11. import javax.inject.Inject;

13. import org.junit.Test;

15. import com.exemple.customerRestFulHibernateWS.services.CustomerService;

17. public class CustomerServiceTest extends KarafContainer

18. {

19. @Inject

20. private CustomerService customerService;

22. /**

23. * Teste la methode getCustomerById(long customerId) de la classe CustomerService

24. * @throws SQLException

25. */

26. @Test

27. public void testGetCustomerById() throws SQLException

28. {

29. Connection connection = dataSource.getConnection();

30. Statement statement = connection.createStatement();

31. ResultSet result = null;

33. try

34. {

35. statement.executeUpdate("insert into ctm.customer (prenom, nom) values('Eddy', 'Sion')");

36. result = statement.executeQuery("select idcustomer, prenom, nom from ctm.customer where nom = 'Sion'");

39. while (result.next())

40. {

41. //Verification que l'enregistrement retourne est celui escompte

42. assertEquals("Customer[idCustomer=" + result.getLong("idcustomer") + ", nom=Sion, prenom=Eddy]", customerService.getCustomerById(result.getLong("idcustomer")).toString());

44. //Verification que la methode retourne null en cas d'enregistrement non trouve

45. assertNull(customerService.getCustomerById(0));

46. }

47. }

48. catch(Exception e)

49. {

50. e.printStackTrace();

51. }

52. finally

53. {

54. result.close();

56. statement.executeUpdate("delete from ctm.customer where nom = 'Sion'");

58. statement.close();

59. connection.close();

60. }

61. }

62. }

Dans ce tutoriel, concernant la classe CustomerServiceTest nous nous limiterons à réaliser les tests unitaires sur la méthode getCustomerById(long customerId). Il va de soi que, dans vos projets, il sera nécessaire de réaliser les tests unitaires sur toutes les méthodes de vos classes.

Méthode de test de la classe CustomerRestServiceTest

Peut-être l'avez-vous remarqué lors de la présentation des classes CustomerDaoTest et CustomerServiceTest, et plus précisément de leur méthode de test testFindCustomer et getCustomerById, on déclare, dans ces méthodes, un attribut du type de l'interface implémentée par la classe à tester. On ne peut pas déclarer une instance de la classe qu'on teste sous peine de voir toutes les méthodes de test en erreur.

Or, dans notre tutoriel « Tutoriel pour développer et déployer un service Web RESTFUL OSGI multibundle sous Eclipse et Karaf », la classe CustomerRestService que nous cherchons à tester n'implémente aucune interface. Il est donc nécessaire de créer une interface qui sera implémentée par la classe CustomerRestService.

Pour cela,

1. dans le module « module-jaxrs », créez un nouveau package « com.exemple.customerRestFulHibernateWSJaxrs.impl » en cliquant droit sur le répertoire « src/main/java », puis en choisissant « New », puis « Package » tel que présenté sur la capture d'écran ci-dessous :

2. Saisissez le nom du package puis cliquez sur le bouton « Finish » :

3. Par un clic droit sur le fichier « CustomerRestService.java », choisissez « Copy » :

4. Par un clic droit sur le nouveau package, choisissez « Paste » :

5. Dans le package « com.exemple.customerRestFulHibernateWS.jaxrs », supprimez le fichier « CustomerRestService.java » en cliquant droit sur le fichier et en choisissant « Delete » :

Confirmez par « Ok » :

6. Renommez le fichier « CustomerRestService.java » en «  CustomerRestServiceImpl.java » en cliquant droit dessus puis en choisissant « Refactor », puis « Rename » :

Entrez alors le nouveau nom :

7. Désormais, vous pouvez créer l'interface CustomerRestService en cliquant droit sur le package « com.exemple.customerRestFulHibernateWS.jaxrs » puis en choisissant « New », puis « Interface » :

Remplissez la fenêtre qui apparaît avec le nom de l'interface :

8. Complétez cette nouvelle interface avec les informations ci-dessous :

module-jaxrs/CustomerRestService.java

1. package com.exemple.customerRestFulHibernateWS.jaxrs;

3. import java.util.List;

5. import javax.ws.rs.core.Response;

7. import com.exemple.customerRestFulHibernateWS.data.model.Customer;

8. import com.exemple.customerRestFulHibernateWS.data.model.Order;

9. import com.exemple.customerRestFulHibernateWS.data.model.Product;

11. public interface CustomerRestService

12. {

13. public Customer getCustomer(long customerId);

14. public Response updateCustomer(Customer customer);

15. public Response addOrderToCustomer(long customerId, Order order);

16. public Response addCustomer(Customer customer);

17. public Response deleteCustomer(long customerId);

18. public List<Order> getOrder(long customerId);

19. public Order getOrder(long customerId,long orderId);

20. public Product getProduct(long customerId, long orderId, long productId);

21. public List<Product> getProducts(long customerId, long orderId);

22. }

9. Modifiez le fichier « my-service.xml » du module « module-jaxrs » pour y insérer la ligne :

<service ref="customerRestService" interface="com.exemple.customerRestFulHibernateWS.jaxrs.CustomerRestService"/>

Le fichier « my-service.xml » ressemble donc à ceci :

module-jaxrs/my-service.xml

1. <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"

2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

3. xmlns:jaxws="http://cxf.apache.org/blueprint/jaxws"

4. xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"

5. xmlns:cxf="http://cxf.apache.org/blueprint/core"

6. xsi:schemaLocation="

7. http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd

8. http://cxf.apache.org/blueprint/jaxws http://cxf.apache.org/schemas/blueprint/jaxws.xsd

9. http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd

10. http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd">

12. <!-- Configuration des logs CXF afin de faire apparaître les requetes recues et les reponses fournies, dans le journal de Karaf -->

13. <cxf:bus id="bus">

14. <cxf:features>

15. <cxf:logging/>

16. </cxf:features>

17. </cxf:bus>

19. <!-- Configuration du endpoint JAX-RS dans le container OSGI-->

20. <jaxrs:server address="/olivier" id="olivier">

21. <jaxrs:serviceBeans>

22. <ref component-id="customerRestService"/>

23. </jaxrs:serviceBeans>

24. </jaxrs:server>

26. <service ref="customerRestService" interface="com.exemple.customerRestFulHibernateWS.jaxrs.CustomerRestService"/>

28. <!-- Declaration du bean customerRestService implemente par le POJO com.exemple.karaf.jaxrs.CustomerRestService-->

29. <!-- Ce bean possede une propriete a injecter. L'injection de cette propriete (ici une instance de la classe CustomerService) est faite

30. immediatement apres la creation du bean  -->

31. <bean id="customerRestService" class="com.exemple.customerRestFulHibernateWS.jaxrs.CustomerRestServiceImpl">

32. <property name="customerService" ref="customerService"/>

33. </bean>

35. <!-- Definition des dependances -->

36. <reference id="customerService" interface="com.exemple.customerRestFulHibernateWS.services.CustomerService"/>

37. </blueprint>

10. Complétez la classe CustomerRestServiceImpl afin qu'elle implémente l'interface CustomerRestService comme le présente la capture d'écran ci-dessous :

11. Complétez le fichier « module-test.pom.xml » pour ajouter la dépendance concernant le module « module-jaxrs » :

module-test/pom.xml

1. <dependencies>

2. <dependency>

3. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

4. <artifactId>module-data</artifactId>

5. <version>[1.0.0,2.0)</version>

6. <type>bundle</type>

7. </dependency>

9. <dependency>

10. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

11. <artifactId>module-services</artifactId>

12. <version>[1.0.0,2.0)</version>

13. <type>bundle</type>

14. </dependency>

16. <dependency>

17. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

18. <artifactId>module-jaxrs</artifactId>

19. <version>[1.0.0,2.0)</version>

20. <type>bundle</type>

21. </dependency>

12. Désormais, nous pouvons créer, dans le module « module-test », la classe CustomerRestServiceTest qui nous permettra de tester les méthodes déclarées dans l'interface CustomerRestService. Nous créerons cette classe dans le package « com.exemple.customerRestFulHibernateWS.test » de la même manière que nous avons créé les classes de test précédentes :

module-test/CustomerRestServiceTest

1. package com.exemple.customerRestFulHibernateWS.test;

3. import static org.junit.Assert.assertEquals;

4. import static org.junit.Assert.assertNull;

6. import java.sql.Connection;

7. import java.sql.ResultSet;

8. import java.sql.SQLException;

9. import java.sql.Statement;

11. import javax.inject.Inject;

13. import org.junit.Test;

15. import com.exemple.customerRestFulHibernateWS.jaxrs.CustomerRestService;

17. public class CustomerRestServiceTest extends KarafContainer

18. {

19. @Inject

20. private CustomerRestService customerRestService;

22. /**

23. * Teste la methode getCustomer(final long customerId) de la classe CustomerRestService

24. * @throws SQLException

25. */

26. @Test

27. public void testGetCustomer() throws SQLException

28. {

29. Connection connection = dataSource.getConnection();

30. Statement statement = connection.createStatement();

31. ResultSet result = null;

33. try

34. {

35. statement.executeUpdate("insert into ctm.customer (prenom, nom) values('Eddy', 'Sion')");

36. result = statement.executeQuery("select idcustomer, prenom, nom from ctm.customer where nom = 'Sion'");

39. while (result.next())

40. {

41. //Verification que l'enregistrement retourne est celui escompte

42. assertEquals("Customer[idCustomer=" + result.getLong("idcustomer") + ", nom=Sion, prenom=Eddy]", customerRestService.getCustomer(result.getLong("idcustomer")).toString());

44. //Verification que la methode retourne null en cas d'enregistrement non trouve

45. assertNull(customerRestService.getCustomer(0));

46. }

47. }

48. catch(Exception e)

49. {

50. e.printStackTrace();

51. }

52. finally

53. {

54. result.close();

56. statement.executeUpdate("delete from ctm.customer where nom = 'Sion'");

58. statement.close();

59. connection.close();

60. }

61. }

62. }

Dans ce tutoriel, concernant la classe CustomerRestService nous nous limiterons à réaliser les tests unitaires sur la méthode getCustomer(long customerId). Il va de soi que, dans vos projets, il sera nécessaire de réaliser les tests unitaires sur toutes les méthodes de vos classes.

V - Lancement des tests

V-A - Cas où certains modules du projet ont été modifiés

Pour lancer les tests unitaires, il vous est nécessaire, du fait que nous avons modifié le module « module-jaxrs », de recompiler l'ensemble du projet par un clic droit sur le projet puis de choisir « Run As », puis « Maven Build ».

Dans la fenêtre qui apparaît, saisissez « clean install » puis appuyez sur le bouton « run » comme le présente la capture d'écran ci-dessous :

À l'issue de cette compilation, les modifications effectuées dans les modules constituant le projet sont prises en compte et une nouvelle distribution de Karaf personnalisée est générée.

Cette distribution est utilisée par « Pax Exam » pour exécuter les tests unitaires.

Un onglet « JUnit » apparaît dans l'interface d'Eclipse pour faire figurer le résultat des tests :

Si cet onglet n'apparaît pas, choisissez « Show view » dans le menu « Window » d'Eclipse puis « Other » puis, dans le filtre qui apparaît, saisissez « junit ». Sélectionnez alors « JUnit » et appuyez sur le bouton « OK ».

V-B - Cas où seul le module « module-test » a été modifié

Dans le cas où vous avez modifié uniquement le contenu de vos tests unitaires, vous pouvez lancer les tests unitaires en cliquant droit sur l'une de vos classes de test puis en choisissant « Run As », puis « JUnit Test » comme présenté ci-dessous :

VI - Code source de l'exemple

Le code source de l'exemple utilisé dans ce tutoriel est disponible à cette adresse :

https://olivier-rozier.developpez.com/tutoriels/rest/tests-unitaires-karaf-multibundle/fichiers/customerRestFulHibernateWS.zip

Il s'agit du code source utilisé dans le tutoriel « Tutoriel pour développer et déployer un service Web RESTFUL OSGI multibundle sous Eclipse et Karaf » auquel nous avons ajouté les tests unitaires décrits plus haut.

VII - Conclusion

Ce tutoriel, complément du tutoriel « Tutoriel pour développer et déployer un service Web RESTFUL OSGI multibundle sous Eclipse et Karaf » conclut notre apprentissage, sous Eclipse, du développement de services web REST destinés à être déployés sous le container Karaf.

Au fil de ces deux tutoriels, nous avons donc pu voir la manière de découper un service web REST en couches applicatives. Chacune de ces couches peut facilement être développée dans un sous-module Maven. À la fin du développement de ces sous-modules, plusieurs choix de déploiement s'offrent à nous :

- par archive Kar ;

- par feature ;

- par génération d'une distribution personnalisée de Karaf.

C'est ce dernier choix qui se révèle le plus judicieux si on souhaite épargner à l'équipe d'exploitation en charge de l'installation de notre projet :

- la configuration des DataSource Factory ;

- la configuration des sources de données ;

- l'installation d'Hibernate ;

- l'installation de pax-jdbc ;

- l'installation des pilotes de la base de données ;

- l'installation de cxf ;

- le déploiement des bundles ou de l'archive « kar ».

Entendez également par tous ces avantages que l'équipe d'exploitation n'aura pas à se soucier des versions des différentes features. Ce qui est un avantage non négligeable pour l'équipe de développement également puisqu'elle contrôle la version des features intervenant dans le projet de bout en bout.

L'autre avantage non négligeable de l'utilisation d'une distribution personnalisée de Karaf concerne les tests unitaires que nous avons découverts dans le présent tutoriel.

En effet, à l'issue du développement, non seulement les tests unitaires sont effectués, mais ils le sont sur une distribution de Karaf dans laquelle tous les développements sont déjà intégrés.

Il en résulte un gain de temps énorme lors de la phase d'intégration des bundles dans Karaf (avec tout ce que cela implique : installation, tests…) puisqu'elle est réalisée durant la phase de développement.

Nous avons pu constater que les tests unitaires de bundles OSGI sont effectués facilement en utilisant le framework « Pax Exam ». Pour les réaliser, Pax Exam installe un container OSGI et y installe les bundles que nous lui avons indiqués par l'injection des services que nous lui spécifions dans chacune des classes de test.

Je tiens à remercier Mickaël Baron pour la relecture technique de cet article ainsi que f-leb pour la relecture orthographique. Ces courageux font un travail pas toujours très motivant, mais toujours efficacement. Merci à vous !

Index

- A -

- J -

JPA (1)

API (1)

JAXB (1)