My 'ah-ha!' moments about testing in Ruby and Rails came when I really sat down and read the definitive resources on the subject, the Rspec and Cucumber books. I shared your initial disdain of Cucumber, but then I realized that I was looking at the picture from quite the wrong angle.
Basically, Cucumber is about BDD (behaviour driven development) - you use Cucumber to plan your features, what you're going to work on next. Hmm, next you want users to be able to promote posts on a forum or something (to steal an example ;)) So you write something simple.
Given I am logged in
And I can see the post "BDD is awesome"
When I vote the post up
Then the post should have one more vote
And the page should show a message thanking me for my vote.
Note that there's no references to anything code related in there pretty much. That comes in your steps. When you refactor your code, you might have to change your step definitions, but the behaviour (your feature) will never need to change.
Now every time you run your Cucumber feature, you'll pretty much be led through how to test the feature using TDD (test driven development). This is done at a lower level using RSpec.
First run - my first step definition is undefined. Copy the block to define it in say user_steps.rb or even session_steps.rb because it relates to users and their sessions. Now, how do you define that a user is logged in? You can take them through the login process.
Given /^I am logged in$/ do
visit login_path
fill_in :name, :with => 'Joe'
fill_in :password, :with => 'Password'
click_button 'submit'
end
Should be all happy. Second step.
Given /^I can see the post "(.+)"$/ do |name|
visit post_path(Post.find_by_name(name))
end
Again pretty easy. Note that if we totally redo our login process, or how our posts are defined and shown, we don't have to change the behaviour. Third step.
When /^I vote the post up$/ do
pending
end
Here's where you're starting to talk about new functionality, but you don't quite know how it's going to work yet. How do you vote a post up? You might click an image of a +1 or something, which does an ajax post to a controller, which returns JSON, or some such. So now you can move into pure Rspec testing.
- Test your view to make sure the +1 image is displayed,
- Test your controller that it behaves correctly when it receives a given ajax request of the right format (both the happy and unhappy paths - what if an invalid post ID is received? What happens if the user has used up their 25 upvotes in a day? Does it increment the number of votes correctly?)
- Test your javascript that it responds correctly when given a blob of JSON in the right format (does it update the +1 image to show it's been used? (thinking of Google+ here...) Does it show the thank you message? etc.)
All of this doesn't affect the behaviour - but when you're finished dealing with the lower level testing, it will be trivial to fill in the step definition for how to vote a post up. It might be as simple as click_link '+1'
. And the rest of the steps are testing results, which again should be straightforward to do. And when you're done, then you know your feature is complete and finished. If the necessary behaviour changes, you can tweak your feature, else you can tweak your implementation code in perfect safety.
I hope this makes sense. It's all been off the top of my head, but I think it demonstrates the difference between BDD and TDD, and why Cucumber and RSpec serve different needs.