Improving Java unit testing and introducing jrsplenda v0.1.1
Last week, I wrote about the pain of Java unit testing. At the end of it all, I said that I would supply some answers “tomorrow”.
One week and one RailsConf later, I’ll call this “tomorrow”.
In the previous article, I listed a handful of commons Java idioms that reduce testability. Three out of four of the issues may be rolled up into a higher level of abstraction: encapsulation kills. The remaining point may be summarized as Spring XML == code that most developers don’t test. Encapsulation prevents developers from injecting mock objects into their code. Without mocks, the unit under test lacks isolation. Without isolation, your unit test is an integration test at best and a massive FAIL at worse.
If you endured a Computer Science curriculum in college, I’m reasonably sure that the professor of your second or third CS class repeatedly hammered home: encapsulation is a key tenet of good object-oriented design. However, many dynamic languages only provide a brief nod, if any, to the encapsulation gods. For instance, Python does not support private or protected members. While Ruby does, it is trivial to route around those protections. What some may not realize is that, in this respect, Java is little different than Ruby.
Yes, that’s right. Java can be a bit on the promiscuous side of things too.
I’m not sure what you’re “java.policy” file allows in your Java installation but, on OS X, the default allows for Java reflection to route around field and method access privileges. Don’t believe me? Take a gander at the
java.lang.reflect.AccessibleObject class that is the parent of Field, Method, and Constructor reflection objects.
With this, you could write/generate a lot of code to act as wrappers for Java objects to expose private, protected, and package scoped Fields, Methods, and Constructors. While this is somewhat interesting, we can do better.
So why bother using Spring for object creation and dependency injection? No, seriously, think about it. Do you test your Spring XML? I would bet every Java developer who reads this article that, as of the authoring date, that you don’t. Ruby is extremely testable. So why are you writing all of that glue code in XML and not testing it? Write it in a Turing Complete programming language and test it, for crying out loud!
But I digress (however only slightly)…
Ruby excels at DSL creation. This has been demonstrated with awesome effect in the unit testing/specifciation domain. But did you know that Ruby’s testing and specification DSLs (i.e., RSpec) can be used to test Java? Oh, yes, it’s true. And Ruby’s mocking libraries (i.e., Mocha, Flexmock, and RSpec mocking), with their ability to create mocks at runtime with just a line of code simply blow Java away.
So what if we put our chocolate in our peanut butter? JRuby, as of v1.1.2, provides workable Java integration such that a JRuby mock object, generated by Mocha, may be injected into a Java object instantiated in JRuby.
Next we write some java.lang.reflect calls, in JRuby (!!), to wrap each non-public field and method for a given Java object with a JRuby method. Yes, a JRuby method. The generated JRuby method takes advantage of the
java.lang.reflect.AccessibleObject’s features to provide setters (JRuby already provides getters) for Fields and to provide direct access to Methods.
Now we can inject these JRuby-created mocks from Test::Unit or RSPec into almost any part (well, ok, except for final members and local variables) of our Java objects. Suddenly, we’re writing test code that’s a whole lot more readable and we’re writing a lot less of it!
I call the field and method wrappers “Splenda” or “jrsplenda” for JRuby Splenda – to sweeten up Mocha. While there are still some minor limitations as of v0.1.1 (see the README provided in the github project), I’ve already re-written some of the JRuby RSpec from my current project to use Splenda. It works like a champ.
Working sample code has been supplied with jrsplenda in the github project. Nonetheless, it bears mentioning describing it in brief.
Splenda is made up of three helper modules: a MockHelper, FieldHelper, and MethodHelper.
The MockHelper, predictably, supplies mock objects but also provides a facility for generating a mock of a Java class and storing the mock in a member attr named after the Java class
# inside a spec or test.... require 'rubygems' require 'jrsplenda' include JRSplenda::MockHelper splenda_mock_attr 'javax.ejb.SessionContext' # => Creates a @session_context containing a mock for a SessionContext in one line
The FieldHelper wraps all non-public (and non-final) fields for a given Java object. This is awfully handy for injecting your mocks.
#inside a spec or test require 'rubygems' require 'jrsplenda' include JRSplenda::FieldHelper import 'my.fake.TestClass' t = TestClass.new wrap_java_fields t # => Generates setters for all non-public non-final fields of the TestClass instance pointed to by t
The MethodHelper wraps all non-public methods for a given Java object. This one is nice for white box testing of internal methods.
#inside a spec or test require 'rubygems' require 'jrsplenda' include JRSplenda::MethodHelper import 'my.fake.TestClass' t = TestClass.new wrap_java_methods t # => Generates setters for all non-public non-final fields of the TestClass instance pointed to by t
- Java may work in isolation but it is better still with the assistance of a dynamic language
- JRuby, as of v1.1.2, can integrate well with Java
- JRuby + RSpec can test Java code
- Mocha + Splenda make this not only ridiculously easy but ridiculously readable
- Question whether you really need Spring. Dynamic languages running in the Java VM can accommodate your dependency injection needs; they’re far more testable than your Spring XML
The latest version is available on github. Clone the repository and ‘rake install_gem’ to get the gem.
Oh, and Splenda may cause diarhea and bloating. Just sayin’.
Update: Incidentally, Flexmock actually plays very nicely with JRuby. I’ll be adding support for it in Splenda in the next release.
Posted by evan on Sunday, June 01, 2008blog comments powered by Disqus