You'll read about how to
- make use of ScalaTest's neat "Specs" syntax (aka "Behaviour Driven Development"), or any other test framework for that matter
- while leveraging the use of OSGi dependency injection
- and keep the boiler plate code to a minimum!
As many Java old timers, I'm attracted to Scala because of the many good reasons you'll find all over the internet these days, but as many, my company is reluctant to let go of the good old (annoying) Java.
So one simple compromise we found was to use Scala to write our automated tests; It won't break any production code, and yet allow us to get our hands dirty with some actual Scala code and know better where we want to go from there.
That only, I felt, was an idea worth sharing because It seems like many of us are in a similar situation!
If it turns out that our tests are more concise, easier to write and maintain and fully compatible with our code base, it should open the doors for introducing Scala into production code.
Now comes the meat.
The code I'd like to test is packaged a "service oriented OSGi bundle". By that, I mean that the code must run in an OSGi environment, so that the service i'm testing is properly injected with its dependencies (and their transitive dependencies).
While this is has many benefits in terms of architecture, it can quickly become a bummer when it comes to tests. Testing outside of OSGi, I would need to mock and/or wire everything by hand, which ends up in a lot of boilerplate code for my test (and I'm lazy).
So there we go!
Step one: create an activator to delay the test Runner
As I mentioned we'll use ScalaTest (but we could use any other), but since we are running in OSGi we can't simply launch the Runner directly. The point here, is to wait for our service dependencies to be available before we run the tests.
I could just use a plain ServiceTracker here, but I'm still lazy, so I'll use the great DependencyManager library by Marcel Offermans to do the work for me.
I haven't found a way to directly inject the dependencies into the test class itself because the Runner will instantiate this class so instead I'll just keep my dependencies accessible via a separate singleton object. (The cake pattern won't help me here because dependencies are wired dynamically after the creation of the object)
I want to start running my tests, only once all my dependencies are available, so I'll call the test Runner from the start method of my Dependencies object.
import org.osgi.framework.Bundle import org.osgi.framework.BundleContext import org.apache.felix.dm.DependencyActivatorBase import org.apache.felix.dm.DependencyManager import org.scalatest.tools.Runner import scala.actors.Actor._ class Activator extends DependencyActivatorBase { override def init(bc:BundleContext, dm:DependencyManager) = { dm.add(createComponent .setImplementation(Dependencies) .add(createServiceDependency .setService(classOf[SomeService]) .setAutoConfig("service") .setRequired(true))) } override def destroy(bc:BundleContext, dm:DependencyManager) = Unit } object Dependencies { var service:SomeService = _ def start = actor { Runner.run(Array("-o", "-s", classOf[SomeServiceSpec].getName)) System.exit(0) } } // note: if you're using the previous (/current) version of DependencyManager: // - the package is org.apache.felix.dependencymanager // - use createService instead of createComponent
Notice here that the test Runner is launched in a separate thread (using the actor block) so that we're not holding a lock in the framework's main thread while starting our service.(The "-o" option will output the tests results to the console instead of launching the graphical interface)
And that's it for the boiler plate! Appreciate that the service I'm about to test (SomeService) could have a dozen of dependencies, which could also have dependencies... whatever the complexity behind, I'll start running my test only once everything is properly wired!
And I don't need to mock anything; I'm testing the real thing. Though I could still use mocks if I wanted to, to fill in for some missing service, and I would do that from the same Activator, by simply registering the mock services into the OSGi registry and letting the wiring happen the same way.
Step two: write the actual test
Now comes the treat; a nice clean BDD test focusing only on what it needs to test and not on how to mock the missing parts, in which you can use/learn the full functional and DSL power of Scala!
import org.scalatest.FlatSpec import org.scalatest.matchers.ShouldMatchers class SomeServiceSpec extends FlatSpec with ShouldMatchers { "SomeService" should "say Hi in return to hello" in { Dependencies.service.hello should equal ("Hi") } it should "say 'See you' in return to bye" in { Dependencies.service.bye should equal ("See you") } }
Please forgive the terrible lack of imagination regarding the purpose of the test, but that's not the point. Simply notice how the tested service is accessed via the Dependencies holder.
Step three: wrapping it up
Before wrapping up the post, we'll need to wrap some library jars to turn them into OSGi bundles!
The Bnd tool by Peter Kriens is your friend here. On top of building your own OSGi bundles, it can turn any plain old jar into an OSGi bundles with one simple command.
The scala library used to be provided as an OSGi bundle (I'm pretty sure!) but I can't find it anymore. Instead, the OSGi packaging seems to be targeting specifically the Eclipse environment, and what they did was to package the scala-compiler.jar as an OSGi bundle and add a Require-Bundle to include scala.library! Pretty strange if you ask me. And to make things worse there's also an Require-Bundle on org.apache.ant! That's close to evil!
Anyway, we won't bother and simply run:
> java -jar bnd.jar wrap scala-library.jar > mv scala-library.bar scala-library-osgi.jar
Similarly for scalatest:
> java -jar bnd.jar wrap scalatest-1.3.jar > mv scalatest-1.3.bar scalatest-1.3.osgi.jar
Now there's an additional trick for the ScalaTest bundle: It will need to dynamically load the test code from our bundle which it doesn't know anything about. So we'll add the -evil but sometimes you have to- "DynamicImport-Package: *" header to its Manifest, otherwise we'll run into a ClassNotFoundException when loading the test.
Finally package your own test bundle. And don't forget to export your test's package so that ScalaTest can load it!
The bnd directives could look like:
Bundle-Name: My First OSGi Scala Test Bundle-SymbolicName: me.myself.service.test Export-Package: me.myself.service.test Bundle-Activator: me.myself.service.test.Activator
Step four: create a launcher for the OSGi framework
This optional actually. I'm using Felix, but I don't want to use a configuration file. For testing I'd rather simply give the list of required bundles as an argument to the launcher. So let's write a quick custom Launcher:
import java.util.HashMap import java.util.ServiceLoader import org.osgi.framework.launch.FrameworkFactory import org.apache.felix.main.AutoProcessor object Launcher { def main(bundles:Array[String]) = { val factory:FrameworkFactory = ServiceLoader.load(classOf[FrameworkFactory], getClass.getClassLoader).iterator.next val felixProps = new HashMap[String, String] felixProps.put("felix.auto.start", bundles.map("file:"+_).mkString(" ")) felixProps.put("org.osgi.framework.bootdelegation", "sun.*") val felix = factory.newFramework(felixProps) felix.init AutoProcessor.process(felixProps, felix.getBundleContext) felix.start } }
This is quite close to the Java version. Simply notice the nice one-liner to set the list of auto.start bundles.
Also notice the explicit bootdelegation on sun.* because the scala library makes use of some sun classes which are not automatically re-exported by the framework.
Step five: finally run your test!
Assuming we've packaged our test into me.myself.service.test.jar, and the service we're testing is in me.myself.service.jar :
scala \ -classpath org.apache.felix.main.jar:classes/ \ me.myself.service.test.Launcher \ me.myself.service.test.jar \ me.myself.service.jar \ org.apache.felix.main.jar\ org.apache.felix.dependencymanager.jar\ org.osgi.compendium.jar\ scala-library.osgi.jar\ scalatest-1.3.osgi.jar
This should output something like:
Run starting. Expected test count is: 2 SomeServiceSpec: SomeService - should say Hi in return to hello - should say 'See you' in return to bye Run completed in 158 milliseconds. Total number of tests run: 2 Suites: completed 1, aborted 0 Tests: succeeded 2, failed 0, ignored 0, pending 0
That's it for my thought of the day!
It won't feed the poor or stop the wars, but don't hesitate to drop a note if you found it useful :)
Any suggestion on how to make this sexier-stronger-faster-lighter-better is naturally welcome!