Acceptance Testing in Ruby, Rails, RSpec and Cucumber
Friday, November 21st, 2008I’ve written up a new post at the Brightbox blog detailing how we are using RSpec and Cucumber to build acceptance tests for the next generation Brightbox systems.
precision engineering for your website
I’ve written up a new post at the Brightbox blog detailing how we are using RSpec and Cucumber to build acceptance tests for the next generation Brightbox systems.
I’ve done a quick write-up on the recent CSRF vulnerability on the Brightbox blog.
Last night I gave a talk at Geekup about RSpec and RSpec User Stories.
Thanks to Ashley Moran for talking it through with me.
UPDATED to use Slideshare to display the slides.
Rails 2.2 deprecates the inbuilt Rails MySql driver and you are recommended to use the native MySql gem instead.
Unfortunately, when I tried to install this on my fresh Leopard box I got:
Error installing mysql:
ERROR: Failed to build gem native extension.
Followed by a load of guff about options.
After a bit of searching I found a post on the Rails blog with some potential alternative commands; mostly about Windows installations but also some for OSX.
Of these, only one worked for me – and that was:
sudo gem install mysql -- --with-mysql-config
Note that that is double-dash space double-dashwith-mysql-config.
Two (related) thoughts on “The Specification is the Documentation“.
One of the things that I like to do, when developing, is to start with a sketch (you know, with 95g/m2 paper and a 6B pencil) of how the UI will look. There are two reasons for this. Firstly, it helps communications with the client – they can see something concrete, while it’s also blatantly apparent that there’s a long way to go yet. Secondly, it puts me in the position of starting from the ideal position and working “downwards” to what is possible, rather than starting from what is easy and working “upwards” to what is nice. In other words it forces me to raise my standards.
Writing my specifications with the helper methods as described is the coding equivalent. I start with a high-level, abstract, description of the problem I am trying to solve (“given a logged in user and three gizmos, expect the gizmos to be tagged when I go to the ‘tag-gizmos’ page”). I then work on setting up the environment (implementing the helpers) and the getting the specification to pass (implementing the code). Again, I start with the ideal position and work downwards as I implement.
The second thing is that it is one of those techniques where I am sure that I am along the right lines. How can I tell? Because as soon as I wrote my first specification in this way it “felt right”. After I had written a couple more I wanted to go back to every piece of code I had ever written and rewrite it all in this new way.
In a former life I used to write “functional specifications”. These were long, dense, hard-to-read documents that detailed what an application (not yet written) was supposed to do. I would spend (literally) weeks typing these things up, the customer would read it, think they understand and I would quote them based upon the document. And then the project would go over budget as all the tiny subtle details became apparent about two weeks before deadline day. But, even worse, the document would slowly become out of date, as changes were made in response to feedback – while the spec stayed unchanged.
As you might expect, this lead to general disillusionment with functional specifications. What’s the point if they didn’t help with the budget and didn’t reflect the actual application?
But when I read about Test-Driven Development the key thing that struck me was the tests became a living embodiment of what the application is supposed to do. They are a specification; but not only that, they are a specification that has to remain up to date.
Only one problem. They are written in code. Making them meaningless to the client. In fact, often, some tests were so obscure they were only meaningful to me; as I was writing them. Come back to it six months later and try and figure out why that change has made it fail? No idea.
Which is why RSpec and its Stories are so exciting. A Story is a text document that describes the required behaviour of the application. Read that again. A text document; written in English. So your clients can read them. Can help to write them. You then supply some Ruby code, that matches the sentences in your stories and associates them with code. That code is run, testing your application and proves that it does what it is supposed to (providing you’ve written your test code correctly of course).
Story: measure progress towards registration goals
As a conference organizer
I want to see a report of registrations
So that I can measure progress towards registration goals
Scenario: one registration shows as 1%
Given a goal of 200 registrations
When 1 attendee registers
Then the goal should be 1% achieved
Scenario: one registration less than the goal shows as 99%
Given a goal of 200 registrations
When 199 attendees register
Then the goal should be 99% achieved
Now, I have to admit, I’ve not used RSpec Stories in anger yet. But it has had a strong effect on my “unit” tests.
The difference between stories and unit tests are that stories test your full stack. Go to ‘/’, fill out the text fields, click the submit button, it should insert into the database successfully and then show these three records on the ‘/whatever’ page. Unit tests will check that the form is shown when you go to ‘/’. A separate test will check that your record can be inserted into the database. Another test will prove that ‘/whatever’ asks the database for three records.
But, having read about Stories, the way I write my unit tests have changed. Check this controller specification out:
describe ReportsController, "at the admin site" do
it "should show this month by default" do
given_the_admin_site
given_a_system_user
when_getting :index do
expect_to_find_orders_for Date.today
end
assigns[:date].should be_today
assigns[:orders].should == @orders
end
end
Not quite plain English. But, when it comes to maintenance, using given, expect and when as prefixes for your helpers makes a world of difference.
UPDATE: now using block syntax around the “when_getting” statement
One of the great advantages of using mock objects to test and specify your objects is that you concentrate solely on the thing you are testing.
If you weren’t using mocks to tests that a controller re-shows the “new” form if given an invalid object, you would do post :create, :model => { ... } where … is a set of fields that are invalid. This means that, when writing your spec, you have to remember what it is that makes that model invalid. This also means that, every time you change that model, and its rules for validity, you potentially have to amend the controller test as well. In other words, you are not actually testing the controller in isolation, you are also testing the model.
Using mocks lets you write the following:
@model = mock Model
Model.should_receive(:new).and_return(@model)
@model.should_receive(:save).and_return(false)
post :create, :model => { :some => :fields }
response.should render_template('/models/new')
In other words, it doesn’t matter what parameters you send to create. The Model (class) will return you a mock instance of model, and the mock instance will return false on save (you can actually get save! to raise an ActiveRecord::RecordInvalid – but I’ve had some difficulties with that). In other words, the real model is no longer part of the test and you are concentrating on the behaviour of the controller alone.
This concentrating on what is important is a vital advantage when using mocks.
But how does this work on testing models?
I wanted to test that a default value was copied from one field to another under certain circumstances. So I set up a method, called set_default_value and wrote some specs to ensure that it was working. Then I wrote the following:
it "should set the default value before validation" do
@model = Model.new
@model.should_receive(:set_default_value)
@model.valid?
end
This failed, as it should do. Then I set a before_validation :set_default_value on the model and the spec passed. Each spec concentrates purely on what is important – a couple to show that set_default_value works under different circumstances, and one to show that it is called when it is supposed to be.
What about testing a :dependent => :destroy association?
Unfortunately, that can’t be done without saving at least one of the objects in the association (which means knowing enough about it to make it valid). But, as David Chelimsky (Mister RSpec) points out on the RSpec mailing list, you can do it using mock objects for part of the association. Which was a relief as my “child” object was complex with a whole set of interdependencies of its own.
A very interesting article about how DRY you should be in your specs.
http://lindsaar.net/2008/6/24/tip-24-being-clever-in-specs-is-for-dummies
Personally I agree with everything said. Readability comes first, even at the expense of efficiency and DRY; “be nice to those who have to maintain the code”. The really interesting thing though is the example is actually quite DRY – it’s more about how you organise and order the code, more than repeating yourself.
Sometimes, it’s worth stating the basics for all to see:
I’ve had this before so I should have learnt my lesson. But I didn’t.
I had a site, inherited from another developer, that was migrated to a new server.
The other developer sent me the images associated with the site and I dutifully copied them over. I took a look – lots of “missing image” place-holders, but as this was a work in progress, in a database of thousands of items, I didn’t know what should be there and shouldn’t.
So I asked the other developer to check things over.
“No – there should be much more than that” he said.
“But look – they are there – in the correct folder on the server!” I replied.
“You’re right, ” he said, “but why aren’t they on the site?”
It turns out that the code for deciding whether to show the product image or the place-holder looked something like this (with superfluous guff removed):
if File.exists?("#{RAILS_ROOT}/public#{product_image(img, true)}")
"<img src="http://blog.3hv.co.uk/wp-admin/#{product.image_filename}" />"
else
"<img src="http://blog.3hv.co.uk/images/products/image_missing.jpg" />"
end
I spent a while playing around, using script/console on the server (a truly fantastic tool). I asked it to generate the image tag for a given product – it returned exactly what I was expecting. I looked at the equivalent page in the application – it returned the image missing place-holder. But mongrel was running as the same user as script/console – there couldn’t be a permissions issue could there?
Then I remembered …
That was the issue – the file system was getting confused as mongrel gave it a relative path for RAILS_ROOT (ignoring the fact that it should be able to resolve it, no matter how many double dots there were). And even worse, things appeared to be working fine until it was deployed to the webserver.
The fix is simple – add the following to your application controller:
helper_method :rails_root
def rails_root
File.expand_path RAILS_ROOT
end
and then replace all instances of RAILS_ROOT within your controllers, views and helpers with rails_root. Problem solved.