6
votes

What's the best way to balance declared exports of packages from a bundle and the requirements of unit testing?

Consider a bundle 'mybundle' for which you want to write unit tests. The bundle's source is stored in an IDE's 'project' concept. For instance:

mybundle
  src/java
    mybundle.package1
      ...java
  bnd.bnd

By unit tests I mean tests of individual POJOs, irrelevant to the wider OSGi context in which these classes may be used within a bundle. Where possible, the tests should be runnable with 'vanilla' classloading, such as by the JUnit runner in Eclipse.

At development time, here are a few ways of packaging the unit tests:

Unit tests in the bundle source

Here, the unit tests are added to a source folder in the project:

mybundle
  src/java
    mybundle.package1
      ...java
  test/java
    mybundle.package1.test
      ...java
  bnd.bnd

Note the appended '.test' to differentiate packages and avoid split package problems.

Typically there will be some measure to ensure test classes don't end up in the built bundle JAR.

Unit tests in a separate bundle

Here a separate bundle is added, name suffixed with '.test' as is the general convention.

mybundle
  src/java
    mybundle.package1
      ...java
mybundle.test
  test/java
    mybundle.package1.test
      ...java
  bnd.bnd

The trouble with this is that because the classes are separated, and the running of unit tests may not be knowledgeable of the OSGi environment (e.g. using the Eclipse JUnit runner) you will have to decorate the runtime classpath of the JUnit runner.

Unit tests in a fragment

(Thanks @HollyCummins). Here a separate bundle fragment is created:

mybundle.fragment
  test/java
    mybundle.package1.test
      ...java
  bnd.bnd

The fragment declares 'mybundle' as its host, thus allowing it to share the classes in 'mybundle' without the need of the packages being exported.

The disadvantage of this is that because fragment loading is an OSGi concept, you need to run with an OSGi container or decorate the classpath.

Export problems

The problem comes when considering how bundles perform Export-Package. It is considered good practice to export as few packages as possible. And yet it seems that unit testing forces extra packages to be exported.

This is most obvious for the second option, having a separate testing bundle. The tests in the testing bundle must Import-Package the classes under test, and the bundle under test must also Export-Package all said classes under test.

So the obvious solution is to lean toward having unit tests in the bundle source, but problems soon crop up in non-trivial situations. You may want to share testing code, for instance you might have OSGi integration tests in a separate bundle. To share code you end up having to Export-Package the testing packages, and then of course you also end up with testing code in the built bundle!

What is the best way to organise OSGi bundles/projects for testing?

3
A third option is to use fragment for your unit tests. This ensures your tests share a classloader with the code to be tested, so no need for extra package exports of internal packages. The test fragment can even export the internal packages of the main bundle if needed. The fragment will have its own package imports, so it can pull in shared test code without contaminating the package imports of the main bundle.Holly Cummins
@HollyCummins great idea! If that works it may be worth promoting that comment to an answer. I can't test it yet because bndtools (the helper I'm currently using) isn't supporting resolution of fragments in v2 alpha at the moment, so I'll have to wait until I can try that.Dan Gravell
Actually, if these are unit tests for POJOs (as I'm most interested atm), not integration tests using the OSGi framework, I would also need to decorate the run path of the unit tests with the sources under test, right? I'll add some clarifications to the question plus your other suggestion.Dan Gravell

3 Answers

0
votes

A third option is to use OSGi fragments for your unit tests. This ensures your tests share a classloader with the code to be tested, so no need for extra package exports of internal packages. The test fragment can even export the internal packages of the main bundle if needed. The fragment will have its own package imports, so it can pull in shared test code without contaminating the package imports of the main bundle.

As mentioned in the comments above and updated original question, using fragments still leaves you with some questions about how you handle your build and classpath. If you're running your tests outside an OSGi container the classloader advantages of fragments largely go away, except perhaps for pulling in imported test dependencies in your IDE.

If you're running your tests in an OSGi container, fragments do have some disadvantages compared to normal bundles which may be a problem, depending how you're driving your tests. Fragments can't declare an Activator, since they don't have an independent lifecycle. Declarative services also can't be registered from a fragment in a natural way, although Blueprint services usually can.

0
votes

Using Maven would make option 1 very simple to implement. The advantage is that Maven manages your classpaths for you, so any code or dependencies that are only required for testing will not end up in the final bundle. You can even place your unit-tests in the same package as the classes under test, so that you have access to package-private classes from your tests. Since the tests are executed using normal classloading, Export-Packages does not affect it at all.

0
votes

If you're testing classes in splendid isolation (as a lot of unit tests typically do) why bother packaging and running such unit tests in an OSGi framework at all. Just compile the class, compile a corresponding test class and run them in whatever test framework you prefer.