Testing Java with JRuby
Chris chided me in the Java only gets 10mpg comments for not giving Groovy or Rhino a go. Chris has a fondness for busting my chops equaled by few. However, he does it because he cares, so I thought a bit about using non-Java languages on the JVM.
I still think that in general, I have little use for the Java platform. If I want to write code in something like Ruby or Python, I’ll just use Ruby or Python or whatever as they’re originally distributed. Bolting them on to the JVM really doesn’t do anything for me. That said, if I can use them to get out of ugly, tedious work, maybe they’re worth my time.
As it happens, I recently inherited about a quarter million lines of Java code. There are chunks of it that are somewhat lacking in terms of unit tests, and I’m one of those mutants who really doesn’t like to fix code without exerciseing the problem with a test first. With this project, I’ve been having to write hundreds and hundreds of lines of test code to make very simple changes. It’s safe, but it’s not exactly efficient or fun.
So I took Chris up on his suggestion and gave JRuby a look for writing tests. It works surprisingly well. There’s not that much setup work that you need to do to get the environment up and running, and once you do it’s rather nice to not have to slog through the ugly world that is Java the language to get your tests written.
Here’s a very brief sample. What I wanted to do is have a very traditional ant-based project with the standard init->compile->test->dist dependency chain, but sub JUnit out for JRuby and Ruby’s unit test framework. First, the silly little Java class that I used for testing:
src/java/org/obfsucated/example/Overbearing.java
package org.obfuscated.example;
public class Overbearing {
/** Javaize a string.
**
** Everybody knows that Java people can't stand data unless
** it's XML or some sort of proprietary binary format that
** you have to pay Sun $10,000/seat to use. Our Overbearing
** class should be able to take any pedestrian string and
** turn it in to something that any Java programmer would
** love and cherish forever.
**
** @param original an ugly, non-Enterprise-compliant string.
** @returns a String that you could put in some sort of bean.
*/
public String javaize(String original) {
if(null == original)
return null;
StringBuilder sb = new StringBuilder();
sb.append("<java-is-rad>< ![CDATA[”);
sb.append(original);
sb.append(”]]> </java-is-rad>”);
return sb.toString();
}
}
And here are the relevant Ruby source files:
src/jruby/test_all.rb
require 'test_overbearing'
src/jruby/test_overbearing.rb
require 'test/unit'
require 'java'
Overbearing = org.obfuscated.example.Overbearing
class TestOverbearing < Test::Unit::TestCase
def setup
@overbearing = Overbearing.new
end
def teardown
@overbearing = nil
end
def test_creation
assert_not_nil @overbearing, "We should be able to create an Overbearing object"
end
def test_simple_string
original = "Hello World"
expected = "<java-is-rad><![CDATA[Hello World]]></java-is-rad>”
resultant = @overbearing.javaize(original)
assert_equal resultant, expected
end
def test_nil_string
original = nil
expected = nil
resultant = @overbearing.javaize(original)
assert_equal resultant, expected, “Nil strings should stay that way”
end
end
It’s all pretty staraightforward stuff. I like to run unit test suites by requiring all of my tests in one file and just calling that with the interpreter. (Thus the “test_all.rb” file.) There are without doubt better ways to do this. You get the general idea, though.
One gotcha is that if you have a package with calital letters, you can’t alias it as above. So let’s say you have a class with a fully-qualified name of org.obfuscated.BadPackageName.Whatever. JRuby — or Ruby itself, if I had to guess based on the way identifiers are usually handled — will not be able to find it if you just say
Whatever = org.obfuscated.BadPackageName.Whatever
Instead, use the include_class function to make sure it’s loaded.
include_class('org.obfuscated.BadPackageName.Whatever')
Long story short, though: don’t use capital letters in package names.
The Ant setup isn’t too difficult, either. Assume the following properties are available:
- jruby.dir
- The directory where JRuby is installed
- jruby.test.dir
- The directory where you keep your test scripts (testall.rb and testoverbearing.rb in this case)
<path id="jruby.classpath">
<fileset dir="${jruby.dir}/lib">
<include name="*.jar"/>
</fileset>
</path>
<target name="test" depends="compile">
<java classname="org.jruby.Main" fork="true" failonerror="true">
<classpath refid="project.classpath"/>
<classpath refid="jruby.classpath"/>
<sysproperty key="jruby.base" value="${jruby.dir}"/>
<sysproperty key="jruby.home" value="${jruby.dir}"/>
<sysproperty key="jruby.script" value="jruby"/>
<sysproperty key="jruby.shell" value="/bin/sh"/>
<arg value="-I"/>
<arg value="${jruby.test.dir}"/>
<arg value="${jruby.test.dir}/test_all.rb"/>
</java>
</target>
Type “ant test” and away you go. Now you now longer have to slog through things with Java, and you’ve removed the compile phase from your test cycle. Bonus.
So thanks, Chris. That was a fine idea.
March 29th, 2007 at 1:59 am
> Chris has a fondness for busting my chops equaled by few.
It’s a calling.
> I still think that in general, I have little use for the Java platform. If I want to write code in something like Ruby or Python, I’ll just use Ruby or Python or whatever as they’re originally distributed. Bolting them on to the JVM really doesn’t do anything for me.
So, I think depends on context. The JVM is the closest thing we have to a universal object-oriented linker and runtime, and it has the advantage of doing dynamic binding to boot. You step away from that and you are in to evil C land with all that entails. On top of that it is a pretty fast runtime (yeah, it sucks for dynamically typed code, but that looks to be fixed in the foreseeable future). So, for right now, it is the best gateway to “other people’s code”, which admittedly, you might have little use for. ;-)
> So I took Chris up on his suggestion and gave JRuby a look for writing tests. It works surprisingly well.
I was under the impression JRuby actually works better than regular Ruby when it comes to Unicode, but I could be wrong.
> …and you’ve removed the compile phase from your test cycle.
No you haven’t. That’s even less valid than notions that Java compiles faster than C (which can be semi-true). All you’ve done is removed the compile phase out of Ant. Really all you’ve ensured is that the code gets compiled each and every time you run it, rather than just the times where you’ve changed the code. Now, I’m all for having the computer do the work and hiding it from the humans, but don’t kid yourself, there is compilation going on there.
> So thanks, Chris. That was a fine idea.
It’s all part of the service.
In answer to the: Groovy suffers from the “if I want to use Ruby, I’ll use Ruby” problem.
The reason you should look at Groovy and or Javascript is that they are both closer approximations to LISP than Ruby, so as per Greenspun’s Tenth Law, you are saving yourself a lot of work and frustration by using them instead of Ruby. Plus Ruby’s implementation is a bit rougher around the edges than Rhino (can’t speak for Groovy). What Greenspun doesn’t tell you is that if you actually were to use LISP, you’d be out of work with a Ph.D. in AI, so Groovy and Javascript are about as close as you can get without getting burned. ;-)
Some folks think Groovy is going to become a big deal, but of course, all the ones I’ve talked to also think Mac’s are due for a comeback. Me, I think Javascript looks likely to become an even bigger deal going forward, what with Adobe open sourcing their Javascript JIT and Javascript 2.0 just around the corner.
Of course, I say all this and I’ve barely looked at Groovy (just been told I should by my betters), my Javascript is pretty weak, and with my “new language” focus being on Haskell, which is kind of the polar opposite of all these dynamically typed languages.
So, I’m a hypocrite. But you already knew that.
March 29th, 2007 at 8:14 am
I think this gets right to it. Other people’s data? Definitely. Other people’s code? Not so much. Seems easier to just get the data via services or even files or a database than it is to tie yourself down to a particular platform.
True, although pedantic. In practical terms, it means that I don’t have put up with an explicit compile phase when running my tests. Now, in a normal setting where compiles are kept simple and they don’t demand to check 10,000 other things and download files and etc etc etc, this wouldn’t be so big a deal. For my current situation, it saves time even with the (absurdly long) time it takes for Ruby to compile down to byte code each and every time.
Hmm. From the presentation I saw last year, Groovy was pretty much Ruby with a few convenience functions for dealing with Java objects. (Lots of “do what I mean” stuff that seemed to work pretty well, for example.) What is it that makes Groovy more like Lisp than Ruby is?
As I unfortunately found out, JRuby’s implementation is much rougher around the edges, almost to the point where (for testing, at least. It could still be useful for automation) it’s not usable. More about that in another post. (Or not. The problem I had at work — inability to subclass — doesn’t exist at home. Interesting.)
March 30th, 2007 at 7:01 am
Hello there! Stopping by for a few quick points…
Hopefully if you have any issues with JRuby you’ll report them on our bug tracker, via JRuby.org. Thanks for giving it a try :)
March 30th, 2007 at 9:43 am
That was indeed it. I was using the 0.9.2 build at work and compiled from the subversion repository at home. Do the docs mention this? I did a cursory check on the wiki to try to find out if a current build could subclass but didn’t have much luck.
Indeed. With this one, I wanted to make sure that it wasn’t a PEBKAC issue first.
April 2nd, 2007 at 1:54 am
All of these remind me of NetREXX (http://www-306.ibm.com/software/awdtools/netrexx/). It was perhaps the first of these “not Java but compiles to JVM” languages back in the day. I liked it quite a bit (since I also like REXX quite a bit). Unfortunately the single researcher who worked on it lost interest and IBM let it die on the vine. It hasn’t been updated since Java 1.1 days.