RSpec, JRuby and Story Testing Java Code

I’ve long been interested in decent ways of expressing tests in a human-readable format. Not just any humans, but BAs and business reps in particular – the kind of people who will not be interested in slugging through piles of language syntax. I tried Fit sometime ago, and was impressed, but when I came back and revisited it recently, it looked a lot like the community had kind of faded away. Accordingly, I looked around at what else was available, and stumbled across RSpec Now, I want to test Java code, and RSpec is for Ruby (as the R kind of hints), but I was able to get this going under JRuby fairly easily. I couldn’t find any examples of other people doing that, so I thought I’d write it up.


First, why look at RSpec? The big reason is that you write tests in a human-readable fashion, following a format put forward by Dan North (Dan North is one of the major contributors to RSpec, as well as JBehave The tests look kind of like this:

Story: transfer from savings to checking account
As a savings account holder
I want to transfer money from my savings account to my checking account
So that I can get cash easily from an ATM

Scenario: savings account has sufficient funds
Given my savings account balance is $100
And my checking account balance is $10
When I transfer $20 from savings to checking
Then my savings account balance should be $80
And my checking account balance should be $30

Scenario: savings account has insufficient funds
Given my savings account balance is $50
And my checking account balance is $10
When I transfer $60 from savings to checking
Then my savings account balance should be $50
And my checking account balance should be $10

Yes – that’s the test, not the story. You can take that and run it through the RSpec tool, and it will tell you if it passes or fails. And I think even a BA can understand the test.

Okay, there’s a lot of magic going on here. I’ll do my best to walk you through it, but please remember that I’m not a Ruby person – my total experience in Ruby before starting this was about 12 hours (it’s now about 20); don’t pick on my Ruby skills (but do feel free to offer suggestions for improvements).

Get JRuby

The end goal of this is to test Java code. That means my RSpec tests, which are backed by Ruby, need to be able to call my Java code. JRuby to the rescue! JRuby, in case you didn’t know, is a Ruby interpreter for the JVM, which allows for pretty decent integration between Java code and Ruby. Here’s a very simple Ruby script that invokes Java code:

require 'java'
include_class 'java.lang.System'

System.out.println "Hello World"

You can save that as a script and invoke it with jruby – jruby hello.rb – or enter it interactively via jirb. Note that if you do this, the require 'java' step returns false now – there’s a lot of slightly-dated examples on the net which says it should return true, but that apparently changed a little while back.

Get RSpec

JRuby seems to come with RSpec, but it’s a slightly old version – 1.1.0, while the current release is 1.1.3. So the first order of business is to update. Ruby uses a packaging tool called ‘gem’ – you can use gem to download new packages, or update packages, over the web.

$JRUBY_HOME/bin/gem install rspec

Make sure you’re running the ‘gem’ command inside JRuby’s bin – if you’ve got ruby on your system already, you don’t want to get mixed up. (Alternatively, you can make Ruby and JRuby share their gems, if you want).

This will put the RSpec library at $JRUBY_HOME/lib/ruby/gems/1.8/gems/rspec-1.1.3; pay attention to that path, because amongst other things, there are examples there!

Create the test

Okay, I told a slight lie – if we put the test above in a file called 'account', we’re going to want to create a file called 'account.rb'. The example above isn’t valid Ruby, and we need something that is. However, it’s not very big (and it’s going to get smaller):

require 'rubygems'

gem 'rspec'
load 'spec/story.rb'

# don't worry - we'll define accounts later
with_steps_for :accounts do
  # Ruby code for "run the file named like this one, but without the .rb extension"
  run File.expand_path(__FILE__).gsub(".rb","")
end

(RSpec people out there – I just want to point out that it took me about 3 hours to figure out the first 3 lines – couldn’t find any examples of using RSpec as a gem that wasn’t built up with a Rails app. Okay, someone who knew Ruby would figure it out, but it took me a while to work out why I couldn’t run my examples except from within the RSPec examples dir).

You then run the test like so – jruby account.rb – and you should get output like this one (and this is the only example I’m showing in full!):

Arcadia:jrspec robertdw$ ruby account.rb
Running 2 scenarios

Story: transfer from savings to checking account

As a savings account holder
I want to transfer money from my savings account to my checking account
So that I can get cash easily from an ATM

Scenario: savings account has sufficient funds

Given my savings account balance is $100 (PENDING)
And my checking account balance is $10 (PENDING)

When I transfer $20 from savings to checking (PENDING)

Then my savings account balance should be $80 (PENDING)
And my checking account balance should be $30 (PENDING)

Scenario: savings account has insufficient funds

Given my savings account balance is $50 (PENDING)
And my checking account balance is $10 (PENDING)

When I transfer $60 from savings to checking (PENDING)

Then my savings account balance should be $50 (PENDING)
And my checking account balance should be $10 (PENDING)

2 scenarios: 0 succeeded, 0 failed, 2 pending

Pending Steps:
1) transfer from savings to checking account (savings account has sufficient funds): my savings account balance is $100
2) transfer from savings to checking account (savings account has sufficient funds): my checking account balance is $10
3) transfer from savings to checking account (savings account has sufficient funds): I transfer $20 from savings to checking
4) transfer from savings to checking account (savings account has sufficient funds): my savings account balance should be $80
5) transfer from savings to checking account (savings account has sufficient funds): my checking account balance should be $30
6) transfer from savings to checking account (savings account has insufficient funds): my savings account balance is $100
7) transfer from savings to checking account (savings account has insufficient funds): my checking account balance is $10
8) transfer from savings to checking account (savings account has insufficient funds): I transfer $20 from savings to checking
9) transfer from savings to checking account (savings account has insufficient funds): my savings account balance should be $80
10) transfer from savings to checking account (savings account has insufficient funds): my checking account balance should be $30

*phew* Okay, we can see that it’s run the test, but hasn’t exactly done very much. Mostly, it’s said that the test steps were “PENDING”. Pending is RSpec’s way of saying “I don’t know what you mean”. So now we have to tell it.

You tell RSpec what the test means by providing “steps”. These steps are usually provided in separate files, but I’m going to put them all in one step file. First, though, I have to update my test script. It now looks like this:

require 'rubygems'

gem 'rspec'
load 'spec/story.rb'

# This ruby code loads all the scripts inside of the 'steps' directory.
Dir[File.join(File.dirname(__FILE__), "steps/*.rb")].each do |file|
  require file
end

with_steps_for :accounts do
  run File.expand_path(__FILE__).gsub(".rb","")
end

The bit in the middle, of course, is the important bit. So we now create an 'accounts_steps.rb' file, inside a steps directory. And it’s going to look like this:

 require 'rubygems'

gem 'rspec'
load 'spec/story.rb'

# This creates steps for :accounts
steps_for(:accounts) do
  Given("my $account_type account balance is $amount") do |account_type, amount|
  end
  When("I transfer $amount from $source_account to $target_account") do |amount, source_account, target_account|
  end
  Then("my $account_type account balance should be $amount") do |account_type, amount|
  end
end

How do I know what to fill in? The RSpec output from earlier was kind of telling me; essentially, I just create a template. If I run the test again, I get a very simple output back indicating no errors (at least, I do if I’ve done it right…).

This test, however, doesn’t test squat. There’s no actual work done in any of the tests! Just as a place holder, I went back and put the word pending inside each block, like so:

 Given("my $account_type account balance is $amount") do |account_type, amount|
 pending
end

When I run the test again, I get the big (PENDING) warnings again – but at least now I know that RSpec is understanding my test.

At this point, it’s time to start putting some code to test in. Here’s the Java class that I’m going to test:

 package net.twasink.jrspec;

public class Account {
  private final String name;
  private double balance = 0.0; // NEVER use double for monetary values in the real world.

  public Account(String name) {
    this(name, 0.0);
  }

  public Account(String name, double openingBalance) {
    this.name = name;
    this.balance = openingBalance;
  }

  public String getName() { return name; }
  public double getBalance() { return balance; }

  public void transfer(Account destination, double amount) {
    if (amount <= 0.0) { throw new IllegalArgumentException("amount must be positive"); }
    if (balance < amount) { throw new IllegalStateException("Insufficent balance"); }

    this.balance -= amount;
    destination.balance += amount;
  }
}

I compile that, and I put the results on my classpath. It’s now available for JRuby to use. (Actually, I’ve got a shell script to do this for me; it’s all in the archive at the end). So now I can start making Java calls. And what I end up with is this:

 require 'rubygems'

gem 'rspec'
load 'spec/story.rb'

require 'java'

include_class 'net.twasink.jrspec.Account'

# This creates steps for :accounts
steps_for(:accounts) do
  Given("my $account_type account balance is $amount") do |account_type, amount|
    create_account(account_type, amount[1..-1].to_f)
  end
  When("I transfer $amount from $source_account to $target_account") do |amount, source_account, target_account|
    begin
      get_account(source_account).transfer(get_account(target_account), amount[1..-1].to_f)
    rescue
    end
  end
  Then("my $account_type account balance should be $amount") do |account_type, amount|
    get_account(account_type).balance.should == (amount[1..-1].to_f)
  end
end

def create_account(account_type, amount)
  account = Account.new(account_type, amount)
  @accounts_hash ||= {}
  @accounts_hash[account_type] = account
end

def get_account(account_type)
  return @accounts_hash[account_type]
end

Run the test again, and everything passes. Just to confirm, I edited some of the values to give me test failures, and sure enough, they were reported as failures.

So there you have it! Real live running RSpec code, testing Java classes.

There’s a little more house-keeping to do, which you’ll see in the archive. First, I created a “helper” file, that would provide support for several stories together. Then, I moved the accounts stories, its steps folder, and the helper, into a sub-folder, to create a “story group”, and added an “all.rb” script that would run all the story groups (as well as a stories.rb script to run all the stories in a group). Finally, I bundled this all up into two Java projects (built via Maven), along with a shell script to run RSpec/JRuby with the right classpath. All in all, not bad for a day and half’s effort. And you can grab a copy of it all if you so desire.

This isn’t perfect by any means. I’ve got a few itches to scratch with this yet:

  • I’d like to find a better way to pass a classpath into JRuby. (Using the JRuby plugin for Maven is out because it wouldn’t see the RSpec gem – I think)
  • I’m very sure my Ruby code could be better.
  • I still need to try this out on a bigger system; my initial target is to fire up a Spring-based application and test it in-situ.
  • I’m focussing on testing backend Java code. Could I take the same test, with a different set of “steps”, and get it to test a webapp (maybe using Watir)
  • I’m not fond of the HTML output for running all tests; I’d rather get something like the JUnit reports from Ant (and Maven) – a summary page, with links to the details. This would involve building a smarter formatter, which would mean learning a bunch more Ruby (attractive in its own right)
  • I’ll probably need to learn how to do set-up and tear-down (setup’s kind of in the “Given” section?)
  • I still need to play with the _other_ type of RSpec test – the model spec This one isn’t quite as powerful a driver for me; there’s no plain-text runner (or is there?), and it’s more in the traditional unit-test scope for Java. Still, it would be useful in some circumstances, so I’d like to know how it works.

Lots of fun to be had. Hope you’ve enjoyed reading this, and can take something away. Please bear in mind that I’m not a Ruby expert, so if you need help troubleshooting this stuff, I suggest you go to the appropriate user communities. OTH, if you’ve got suggestions or improvements on my technique, or comments about the approach, I’d love to hear from you; the comments box is just below.

About these ads

4 thoughts on “RSpec, JRuby and Story Testing Java Code

  1. Tom Adams

    Hi Robert,

    I’d thought Ola Bini had covered using RSpec to spec out Java code, but perhaps not.

    If you’re interested in literate specification for Java (and perhaps Scala) code, might I suggest you create an interface/API for this on top of Instinct? :) It’s a feature I’d like to see round out Instinct’s feature set and not something that is that high on my personal priority list.

    Cheers,
    Tom

    Reply
  2. Robert

    I put a note about this post to the RSpec users group, and Ola Bini did indeed reply with a reference to “JTestR”:http://jtestr.codehaus.org/. However, I didn’t find it before I did this work. :)

    JTestR is brand new – version 0.1 was released 28/12/2008. I’m going to check it out, and if it’s mature enough, move to it – I’ll definitely be keeping an eye on it.

    I’m ashamed to admit it, but I haven’t looked at Instinct yet; I missed that AJUG meeting. However, as my primary interest is having tools to work with (as opposed to writing it), I doubt I’d be able to get the motivation required to lure me away from World Of Warcraft. ;)

    Reply
  3. Michael

    Yes I think FIT fading is a shame. It was/is a great idea. I still think its less brittle then this. This is good for over-the-shoulder scenario testing though.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s