Blog Archives

RSpec 2.6.0 and RCov

Posted in Code, Ruby on Rails, Tips and Tricks

Just upgraded to the newest RSpec (2.6.0) and found that RCov has stopped working completely? That’s what happened to me after running a bundle update. RCov refused to run, no error messages, just blank output.

After looking around for a little while I found this: https://github.com/rspec/rspec-core/issues/370

In short: the new RSpec has been broken up into modules a tad more, the one that we require for rspec to run correctly ‘autorun’ is not included by default, so to solve this simply add require ‘rspec/autorun’ to the top of your spec_helper.

Can’t see this Gist? View it on Github!

Specjour with Custom Bundler and Database Setup

Posted in Code, Inside TFG

We have a test suite here that now is rapidly approaching 2 hours using a single core. Let me just repeat that. A developer realistically would have to leave their machine testing overnight to see if the suite is working. That’s really not good enough.

Specjour has been a bit of a turn-key miracle worker with our RSpec suite, however lately we’ve started to require some custom database setup that we do in a seeds.rb file as well as some custom bundler install parameters as most of our devs don’t have MySQL installed. Both of our needs were being nicely stomped on by Specjour so I thought it was time to look elsewhere.

I took a trip down Hydra lane and while getting to the point of having a working, local, dual runner system was a piece of cake, getting something working remotely via SSH took me hours of pain. Debugging the remote SSH workers was a nightmare and I spent a couple of hours running through code before deciding it was probably better to update our existing solution rather than tooling up a brand new one.

Back to the Specjour code. Specjour includes a rails directory inside which is an init.rb which Rails will run at initialisation (it’s part of what Rails does) but the Specjour initialiser will always just run the default database setup task no matter what initialiser you’ve got setup. We had a specjour initialiser that runs if ENV['PREPARE_DB'] was populated, which it is by Specjour, the problem was that the Specjour initialiser ran in the Rails after_initialization hook and therefore stomped all over our database setup.

The first step was just to have our initialiser write to another ENV element and then to have the Specjour after_initialize handler respect this. This isn’t too hard to implement as the after_initialize handler is just a block that is attached and so inside of this block you just need to check that ENV element. In my case I created a new ENV['DB_PREPPED'] element when my database setup had completed and then when the after_initialize block runs it checks for ENV['DB_PREPPED'] and will do nothing if that’s been set to true.

Easy. I now had Specjour respecting our database setup task.

The next step was to try and test this outside of a Rails application, not only that but to test the operation of a block (anonymous function?). To do this I setup a stub on a mock Rails class and let it capture the after_initialize block and then I ran a number of specs against this block.

module Specjour
  module DbScrub
  end
end

DO_NOT_REQUIRE = true

describe "Rails Initialiser" do
  before :all do
    ENV['PREPARE_DB'] = "true"

    stub(Specjour::DbScrub).scrub

    class Rails
      class << self; attr_accessor :configuration; end
      class << self; attr_accessor :test_block; end
    end

    config = Object.new
    stub(config).after_initialize { |args|
      object = Object.new
      Rails.test_block = args
      object
    }
    Rails.configuration = config

    require 'rails/init'
  end

... tests ...

This code essentially mocks up Rails.configuration and then stubs the after_initialize method. This stub then places the block that after_initialize yields to into Rails.test_block. When I require 'rails/init' it sequentially processes the file (as with all Ruby) and the stub will capture the block. After this is a bunch of tests I run an whether the Specjour::DbScrub.scrub method is called or not, so it’s nothing special.

I felt like at this stage I had fairly well tested the main aspects of the database setup.

The next issue was with how bundler was being handled. We have a situation where we would like to install sometimes without some gems. Some of the gems we use and have written use applications we’d rather not maintain in development and get tested in our staging and production environments. We generally will run a bundle install in development without the production or metrics groups so I wanted to have the ability to pass through a custom bundler command. That’s pretty easy now with my gem. Inside .specjour/bundler.yml there is a command property. I think this is more complex than what’s required, but I can foresee us needing a number of custom rake tasks and shell scripts so this bundler.yml should have probably started life as a settings/commands/something_generic.yml

To test this part of my changes was pretty simple. I basically just stubbed the system calls to bundler to give certain return values and checked to make sure the correct program flow happened.

describe ".bundle_install" do
    let :manager do
      stub.instance_of(Specjour::Manager).project_path { "/tmp" }

      stub(Dir).chdir(anything) { |args|
        args.last.call # This yields to the block for Dir.chdir()
      }

      manager = Specjour::Manager.new
      stub(manager).project_path { "blah" }
      mock(manager).system('bundle lock')

      manager
    end

    it "should perform a bundle lock" do
      stub(manager).system('bundle check > /dev/null') { true }

      manager.bundle_install
    end

    it "should check if there are gems required" do
      mock(manager).system('bundle check > /dev/null') { true }

      manager.bundle_install
    end

    context "when gems are required" do
      before :each do
        # Not a before :all as it needs to hook into the let hook above

        stub(manager).system('bundle check > /dev/null') { false }
      end

      context "and there is a bundler YAML file" do
        before :each do
          config_file = ".specjour/bundler.yml"

          mock(File).exists?(config_file) { true }
          mock(File).read(config_file) { "" }
          mock(YAML).load(anything) {
            { 'command' => "do it" }
          }
        end

        it "should get the bundle command from the YAML file" do
          mock(manager).system('do it > /dev/null')
          manager.bundle_install
        end
      end

      context "and there is no bundler YAML file" do
        before :each do
          mock(File).exists?(".specjour/bundler.yml") { false }
        end

        it "should perform a bundle install" do
          mock(manager).system('bundle install > /dev/null')
          manager.bundle_install
        end
      end
    end
  end

You can see that I stubbed our the Dir.chdir block to just yield directly to the call, otherwise it’ll throw an exception. Then I stubbed and mocked out the Kernel.system calls as necessary. Kernel methods are generally included into Ruby objects so you don’t stub Kernel, you stub the object that has the Kernel methods. Most of the testing is pretty basic, but I’d be keen to hear if I’m doing anything incorrectly!

This was my first major venture into adding functionality to a public project and it was good fun. I think it made me do a little better work than I might normally, it’s a great motivation to potentially have peers look at how you do things.

After bundling it all up and testing it here with over a dozen developers and even more machines I’m pretty happy with how it functions. I’ve made a pull request back to the original gem creator and hopefully he’ll like what I’ve done. In the meantime if you want to check it out then my Specjour is available on Git Hub.

Parallel RSpec Performance Testing

Posted in Inside TFG

We’re currently deciding on hardware to build a bit of a testing cluster on which we’ll run whatever the current best remote testing package we can find. At the moment, for us, that’s ended up being Specjour. We ended up pitting one of our i7 iMacs against a $400 Acer box we bought. We installed Ubuntu’s REE 1.8.7 on the Acer machine and RVM and REE 1.8.7 on the iMac.

i7 iMac – Quad Core Hyperthreading

real 11m14.131s
user 0m0.776s
sys 0m0.348s

Acer – E5200 Dual Core E5200

real 35m56.658s
user 0m0.870s
sys 0m2.500s

It seems like the little Acer box offers slightly better value for money in this case. I’d like to see how we’d do with some virtualisation sitting on top this, but I think the included memory will be quite limiting in this regard. I wonder whether the processor resources are actually being fully utilised.

Running RSpec in Distributed and Parallel using Specjour

Posted in Inside TFG

Just to cut to the chase, Specjour will easily allow you to run your tests in parallel on your local machine, or in a distributed way on your network. It cut our test suite from over 11 minutes to under 2.5 minutes. We went from one Macbook Pro to a Macbook Pro plus an iMac. Specjour is gooood, but continuing …

We were running into issues with our testing suite running past 15 minutes to run. It caused a number of issues, firstly it disuaded people from running the suite for small changes they thought would be okay, secondly people tended to slow down or not work at all when the suite was running. Given we were building tests into an existing code base and coverage was running generally under 30% this was already really worrying.

We’ve been concentrating primarily on functional testing with RSpec so I went looking for solutions for trying to parallelise RSpec specifically. I tried parallel-tests but I couldn’t get it to work. Then I heard about Specjour on a Hashrocket video so I decided to give that a try. It worked pretty well out of the box, I just had to plug up a wayward plugin, update the rsync config file for the project and change one of the files in the gem to call a different rake task.

Specjour essentially uses Bonjour to find workers on the network and dispatch tests to them and it uses rsync to synchronise the project you’re testing to the remote server. Once the sync has happened then runs the rake task to setup the databases, loads the environment and starts accepting the tests that are dispatched to it.

The instructions will work for the majority of people, I only had some hiccups due to our project. Essentially I gem installed specjour which gave me a specjour executable which I ran to create a dRb server that communicates via Bonjour. Then in my project I added specjour to my Gemfile (all managed via Bundler!) and ran rake specjour.

For me, I ended up having a few hundred failing tests, all to do with the Ruby version of a stack overflow (stack level too deep). It turned out it was caused by a plugin calling alias method chain twice so that the original method ended up being aliased to the replacement method and so there was an infinite loop. Once I applied a fix then we were only down to a few failing tests to do with seed data.

I changed the db_scrub.rb file to perform a different rake task for us as we do require some seed data and then we were on our way.

Previously on my Macbook Pro the suite ran in just over 11 minutes, after turning on a four core iMac and my Macbook Pro I had six workers ready to go and it took about 2.5 minutes to run our tests. I’d really like to crank up the other few iMacs and the 10 or so Macbooks and see what happens then.

I think the only changes we’ll require is to be able to specify which task is run to create the databases, other than that it works perfectly.

In closing I’d say I tried a number of distributed and parallel methods for running our tests and I like Specjour the best. Apparently it will run Cucumber tests as well, I’m hoping to move on to that soon enough.

Using RSpec Example Groups for Common Functionality

Posted in Inside TFG

I’m currently getting into using RSpec for testing our controllers on what is turning into a large project. It’s been more than handy because we have a lot of complex scoping to take into account whenever retrieving data. People don’t like to see other peoples’ financial data, mostly because it implies that someone is probably looking at theirs. With this in mind it’s more than important that we know the right data is going to the right places and hence the need for controller testing.

Now most of our controllers require the user to be logged in so writing tests to check this for every controller is annoying and time consuming, more than that it feels dirty. I think this is what some people call a code smell though I’m not up to speed on buzz words. There are also other tasks that are done quite often such as setting up the various types of users we’d like to test as, it would be nice if this were easily put in one place and could be easily pulled in. I guess I was looking for a template of tests that I could share.

It seems that the solution to it is found in Shared Example Groups which I hadn’t heard very much discussion about and it kind of leaves working out how they work to you rather than documenting it too much.

So far I’ve used it simply to make sure that controllers that require are redirecting users appropriately and also for setting up a specific type of user for our system before testing.

I created a directory under /spec/support called example_groups and in there I have a file called login_groups.rb. In that file I have something like the following :

shared_examples_for "customer is logged in" do
  before(:all) do
    @user = Factory(:customer_user)
    @user.customers.push Factory(:customer)
  end

  before(:each) do
    activate_authlogic
    OperatorSession.create(@operator)
  end
end

Now in my spec files when I have a bunch of tests requiring a logged in customer I will include this little snippet :

  it_should_behave_like "customer is logged in"

I get a logged in customer to start playing around with. I have the spec/support/example_groups directory in my include paths for Rspec and so it just all works.

My tests can then start to look like :

describe MerchantsController do
  it_should_behave_like "areas requiring login"

  context "customer logged in" do
    it_should_behave_like "customer is logged in"

    ... insert other tests here ...
  end
end

It means I can swap in another authentication gem/plugin pretty easily and also encapsulates the logic about creating customers, or whatever type of item you want to use, so that if that changes you can swap things in and out with a minimum of fuss.

Just to be clear, example groups aren’t limited to setup tasks or connecting to before/after hooks, you can also include a bunch of tests as well. This allows me to have a bunch of tests to run to make sure that a user does have to be logged in for various controllers and include these tests in on line.

I hope this helps someone, it took a bit of searching and trial and error myself this morning to get it working and find the uses for it that I’ve found. I’m definitely open to better solutions to this sort of issue though.

Twitter

The latest @rubyfive podcast is up, our own @sj26 receiving a mention for Ruby 1.9.3 performance improvements. http://t.co/hfx3EPMz

@frontiergroup about 1 day ago #

Search Posts

Featured Posts

Categories

Archives

View more archives