Thursday, April 17, 2008

Is OSGi the enemy of JUnit tests?

I wrote a set of JUnit(3.8.1) tests for the terminal. Originally those were normal unit-tests. To be able to test non public methods and non public classes, I put the tests into the same package but into a separate test plugin. I also added the test plugin as friend plugin of the packages I want to test. This works fine if I run the tests as normal JUnit tests. But if I run the same tests as JUnit Plug-in Tests I get java.lang.IllegalAccessError. The reason is simple: each OSGi bundle runs it's own class loader and therefore the classes appear not to be in the same package.

There are different solutions:

  • Make all methods and classes you want to test public (really bad idea)
  • Put the unit tests into the same plugin as your code and make the dependency to JUnit optional (not a good separation of concerns).
  • Only test public classes and methods (I think this is to restrictive and often to coarse grain)
  • Make your test plugin a fragment. One problem is that other plugins cannot access classes defined in fragments (as Patrick Paulin points out in a more detailed discussion about fragments in unit tests). Another problem is that plugin.xml in a fragment is ignored. And therefore you test plugin cannot contribute


I tried out turning a plugin into a fragment. It is as simple as adding adding a line following line to your MANIFEST.MF
Fragment-Host: org.eclipse.the.plugin.you.want.to.test
and removing the plugin you want to test from the required plugins. As long as your test plugin is not part of a bigger test case and it does not need to contribute extensions Patrick Paulin describes a solution for that using reflection, fragments are a good solution.

A good way to avoid having to use extensions (plugin.xml) in your test plugin is to use dependency injection for your classes.

But I think there should be a better way to write a test plugin that can access non public classes and members. I understand why the security concept of OSGi introduces those problems, but I am still looking for a solution for my JUnit tests.

Any ideas?

15 comments:

  1. Try fragment.xml instead of plugin.xml if you need to contribute extensions.

    ReplyDelete
  2. ya, fragments generally are the best solution for tests.

    Another solution is to x-friends the test plug-in

    ReplyDelete
  3. Fragments are the way forward; the only thing is, you need to add a dependency on org.junit in your bundle. I get around this with an optional dependency.

    In terms of plugin.xml - you can contribute things (e.g. mock) into the extension registry directly; you don't need to rely on plugin.xml as a way of getting items in there.

    I had a mechanism which ran through tests, and depending on whether it needed the OSGi environment up and running or not:

    http://alblue.blogspot.com/2006/03/javaeclipse-running-all-tests-in.html

    Alex

    ReplyDelete
  4. Actually, "lazy developers are the enemy of JUnit tests"

    ReplyDelete
  5. If you declare the tests to be a friend, and make the private packages visible to the test plugin, you should be able to run as a Plugin JUnit.

    ReplyDelete
  6. Fragments can contribute extensions if they have fragment.xml and not plugin.xml.

    This might have some quirks, but it works.

    ReplyDelete
  7. Rename your plugin.xml to fragment.xml, then you can define extensions there.

    ReplyDelete
  8. > Make all methods and classes you
    > want to test public (really bad
    > idea)

    Is it? Public not necessarily the same as API.

    The strategy used by many in the Eclipse project is to make *internal* (i.e. non-API) classes and members public if they need to be invoked by tests, and then declare the test plug-in as a friend (true, an Eclipse-ism).

    ReplyDelete
  9. >Another problem is that >plugin.xml in a fragment is >ignored. And therefore you test >plugin cannot contribute

    AFAIK the equivalent of plugin.xml in a fragment is fragment.xml

    ReplyDelete
  10. One could argue that this is a PDE issue rather than an OSGi one. But I agree with all the other commenters: use a fragment.

    Alex, you don't need to add the dependency on JUnit to the original bundle, optionally or otherwise. Just add it to the test fragment. Both Require-Bundle and Import-Package are valid headers in a fragment, and anything you put there is appended to the Requires/Imports of the host bundle.

    ReplyDelete
  11. Hi,
    Probably kind of late for this discussion but my advise would be to use Maven. In this way your tests are in the same bundle but kept under java/test folder. Tests may contain other dependencies than main classes and are not provided in the output artefact.

    ReplyDelete
  12. I looked for some time to find the solution to test OSGi bundles. I wrote the result here:
    http://knol.google.com/k/ed-pichler/osgi-automated-unit-and-integration/294uonxs6koor/14#view

    ReplyDelete
  13. You can mock the BundleContext and services as well.

    ReplyDelete
  14. I never really understand use of OSGi though I heard a lot about it that it can provide sort of dynamic control to you just like JMX you can invoke any method from any class at runtime etc , does any one here OSGi in there project , how do you find this, is it worth to learn ?

    Javin
    How HashMap works in Java

    ReplyDelete
  15. I wonder if anyone has tried Luminus that created some code to expose what is inside the container to another equal bundle that is the testing bundle - http://lsd.luminis.eu/fitnesse-and-osgi/

    ReplyDelete