seawolfsanctuary

home :: blog :: rss feed :: identi.ca / twitter :: diaspora* :: last.fm :: rss

RVM + rSpec = Compatibility Fun!

September
26

rSpec lightbulbRubyists make a decision which version of which flavour is their preferred, either generally or for that project. But, does it not seem that sitting inside their chosen one a little closed-minded and unfriendly to others’ preferences? It does to me, so I’m combining the magic of RVM with my rSpec testing to make my software cross-version — and even cross-Ruby — compatible.

Erm… Ahem.

RVM LogoBefore we go any further I need to check that you took my advice and integrated RVM with your Ruby projects, yeah? Good, I hope it’s been a good experience for you to chop up your development environment into per-project workspaces; if you haven’t, GTFO.

Great: you guys and girls left are the ones I want to deal with. Now you must prepare for your next challenge: combining RVM with that ever-so-awesome testing tool rSpec. Don’t worry if this seems over-the-top for your project; it’s worthy practice in even the most modest of code-bases. I’ve only the smallest of personal projects and only one I deem worthy of such a practice. Coming to the conclusion that I’d like my rubident code to run in more than ten minutes time, I have to make sure it’s going to work the second, third and hundredth time round. How? Test it!

The Project

Embarking on rubident out-of-the-blue meant I hadn’t looked into how a Ruby project could consume the web services and APIs it does so I just went ahead and wrote some code. Yep, I got myself into development-driven testing; I wanted to find my feet in how best to organise it before fighting the dreaded red-coloured output since I didn’t know rSpec at the time either! Around about here rubident looked like this:

  • OOP, not just a script as it once was (turns out doing it the proper way is just as simple!)
  • set multi-service support
  • any number of accounts on those services
  • oAuth (v1) authentication through them
  • reading and writing pretty much all the expected streams; home, replies, direct messages etc.
  • a couple of helpers to sort out the dates
  • hosting on Gitorious and GitHub.

Since the specifications for the project is to eventually support everything the API does (what more can a guy do?) the one thing missing from that list is the big one: tests. I had to get testing up there, so now that I have something to work with I can learn how Rubyists test!

Testing: And So It Begins

At the time of writing, the documentation for the newer rSpec (v2) lacks greatly from that of it’s predecessor, which is quite unsettling for a beginner. This made it quite difficult to pick up rSpec as I didn’t want to read documentation and tutorials for v1 and find out I’d need to re-write my attempts. Admitting defeat, I just wrote some ruddy code. I already had a list of features that rubident did:

  • start a client instance
  • make sure we load some gems (JSON et. al.)
  • make sure we load some files (helpers)
  • support a number of (technologically very similar) services
  • add accounts on those service
  • understand oAuth (ahem, I’m doing this one last…) to add those accounts
  • read various timelines
  • parse and order various stuff out of each stream
  • post through an account

Writing this list made writing the list of tests easy:

  • load ‘rubident.rb’
  • When starting up:

  • it should create a client to work with
  • it should load at least some of the required gems
  • When setting up services and accounts:

  • it should read a file of supported services
  • it should read a file of set-up accounts

I’m sure you can see where I’m going with this; adding this list of requirements as the rSpec file for my main rubident class — spec/rubident_spec.rb — gave me an instant template for testing my code. I’m not going to go into the actual code, hat’s up to you to make sure your tests actually do, but I will say that:

  • it should create a client to work with
  • it “should create a client to work with” do
      @client = Rubident.new
      @client.should_not be nil
    end

The Awesome

Here’s the TL;DR of this post if you’re using RVM, rSpec and have written some code and tests. I’ve plumped for a plain ol’ Bash script to do the following:

  • list all the Rubies installed on my system through RVM, in a machine-readable format (the strings bit)
  • switch to the rubident environment for each installed Ruby and version
  • ensure all my Gems are installed as per the Gemfile
  • run my rSpec tests for this particular Ruby and version, coloured and listing my tests
  • carry on to the next Ruby or version
  • at the end, switch back to my preferred Ruby and version.

for RUBY in `rvm list strings` ; do
  rvm use $RUBY@rubident && bundle install && rspec -cfn spec/*.rb ; echo
done

rvm use 1.9.2@rubident

This gives me 100% compatibility for all my installed Rubies as far as I have tested, in one command. By having older Rubies installed, I can maintain backward-compatibility too. Of course, this is limited to as much backward-compatibility as the gems allow for, and how far my tests go but I’ve found this is enough to inspire my confidence that I am writing well-working code

I immediately saw a failure resulting from changes between versions: MRI 1.8.7 can require both gems and files and load files, whereas v1.9.2 only require gems but still load files. By getting them green without version-specific code, one can ensure it is running at a sort of baseline, without specific features. You can also include optimisations for specific Rubies in your code and have them tested, since we are switching between whatever is installed — but that’s up to how you want to work.


Thanks for reading! I hope you find my post useful if not interesting, and appreciate any (appropriate!) comments.

By the way, I use the term Rubies to group not just all Ruby implementations but also every version of them. I thought it might be useful to clarify this. How do most use the term?