Keeping Your App from Flying Off of the Rails with BDD - Part 1 of Question Mark

The following blarg assumes working knowledge of user story mapping, user stories, and other such techniques of Le Méthode Agile.

Through a nigh omniscient understanding of the needs of The Cash Cat, your product team has painstakingly produced a sublime list of sparklesUser Storiessparkles, seemingly from thin air. Trusting the Wisdom of the Ancients, the development team has chosen to ride the Rails of Ruby forward into user-acquisition glory. How can we ensure that their vision of the app is executed flawlessly?

tadaSkynettada

JK.

What we will do is set up tools (Rails, RSpec, Capybara, FactoryGirl, and Guard, to be precise) and a workflow for building our app using behavior-driven development. This isn’t a guarantee per se that we will deliver literally flawless code, any more than flossing is a guarantee that we’ll never get cavities. HOWEVER structuring our app in this way does give us much better odds of producing robust, low-defect code that delivers on the requirements we set out to build.

Before we go any further:

If you are the kind of person who hates to read, you can jump straight over to the demo repo for this blog post and download everything there, even though it will make me cry. But we both know you’re not that kind of person, right? cry

So What Are We Building, Exactly?

Mantra-like, the steady rhythm of “As a Cash Cat” droning in your mind’s eye, you mentally intone The Stories in preparation for your upcoming sprint:

As a Cash Cat, so that I can claim my rightful place on the Interwebs, I can sign up for a Cash Cats account when I first visit the site.

As a Cash Cat, so that I can recall my mind-numbingly vast sums of cash, I can log into my Cash Cat account.

As a Cash Cat, so that I can show off my wads of dough, I can record my cash when logged in.

As a Cash Cat…

As a Cash Cat…

As a Cash Cat…

Double digit user acquisition rates got me eatin dis here cash money.
Sorry, got a little carried away there. Eating dis money nao.

Let’s get our project set up to test and implement these simple features.

Putting Stuff Together

Start out by laying down some fresh Rails to start driving this train into the future:

rails new cash_cats

Installing the Test Suite

Let’s first switch everything over to RSpec by adding a few gems, and also add Capybara Webkit for Javascript and browser testing:

# Gemfile
group :test do
  gem 'rspec-rails'
  gem 'factory_girl_rails'
  # For dummy data
  gem 'faker'
  gem 'database_cleaner'
  gem 'capybara-webkit'
end

From the command-line, we bundle, set up RSpec, and remove the (now) unused test directory:

$ bundle
$ bundle exec rails generate rspec:install
$ rm -rf test

Next, modify the Rails spec helper to use both Database Cleaner and Capybara Webkit. The boilerplate for Database Cleaner shown below can be found in the README for the repo:

# spec/rails_helper.rb
# ...

Capybara.javascript_driver = :webkit

# ...

RSpec.configure do |config|
  # Other stuff.
  config.use_transactional_fixtures = false

  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do |example|
    DatabaseCleaner.strategy= example.metadata[:js] ? :truncation : :transaction
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end
  # Maybe some more other stuff.
end

At this point, if you run rake from the root of the project, you should see some output indicating that RSpec is running, albeit with 0 examples as the result. One last bit of cleanup before we move on is to update the generators in the application config so that they use RSpec instead of Minitest:

# config/application.rb
module CashCats
  class Application < Rails::Application
    # ...a bunch of other stuff.
    config.generators do |g|
      g.hidden_namespaces << "test_unit"
      g.test_framework :rspec, fixture: false
      g.fixture_replacement :factory_girl
    end
    # blah blah blah more stuff.
  end
end

Now, when we run a generator that creates a test, it will use RSpec and FactoryGirl instead of Minitest and fixtures. Additionally, we hide the test_unit generator namespace so that it doesn’t muddy up the help menu output when rails g is run without any arguments. grapestada

Drive Straight to Town on Rails of Ruby

To test-drive this cat party, we will write out a handful of feature specs, then work on getting them to pass. A method I have found helpful when working with a fairly well-defined set of features is to write out a number of them ahead of time using placeholder specs. This acts both as a todo list of sorts, as well as an indicator of progress. I also find that it helps me to keep a high-level picture of the current application component in mind. Your mileage may vary, etc., etc., [insert other disclaimers and anti-troll bait here].

Let’s make two feature groups:

$ bundle exec rails g rspec:feature login_and_authentication
$ bundle exec rails g rspec:feature recording_munny

…and add a handful of specs to them:

# spec/features/login_and_authentication
require 'rails_helper'

RSpec.feature "Login And Authentication", type: :feature do
  it 'can register for an account'

  context 'after creating an account' do
    it 'can log into my account'
  end
end

# spec/features/recording_munnies
require 'rails_helper'

RSpec.feature "Recording Munnies", type: :feature do
  context 'when logged in' do
    it 'can add munny to my total and show it off on my profile'
  end
end

You may notice that the format of these specs fairly closely matches the format of the user stories. This is intentional: the goal is to map the specs back to the stories as closely as possible. Running rake should now display three pending specs.

Speed Up This Train

With our mini feature suite in place, we are just about ready to drive full speed ahead toward Internet-dominating MVP-dom. But first, let’s stop and make one final improvement to our test cycle. Running rake manually is great and all, but wouldn’t it be more awesomer if we could automate that a bit? Let’s add guard-rspec to the mix to do just that:

# Gemfile
group :development, :test do
  # ...
  gem 'guard'
  gem 'guard-rspec'
  # ...
end

Now bundle, initialize the Guard gem, and start it up:

$ bundle
$ bundle exec guard init
$ bundle exec guard

If all goes as expected, saving a spec file should now trigger a test run for only that file. Keep in mind that this works only for files suffixed with _spec, which is the default for generated specs. Give it a try by opening up one of the two feature spec files and saving it. There are a number of other settings that can be tweaked in Guard to make it focus failed tests, use Spring, etc., but we will skip those features for the sake of this walkthrough.

Holy Moly WOW Let’s Build The Whole Thing Now

NOT SO FAST.

That’s it for the first part of this blog series. In the follow-up to this post, we’ll go about implementing the actual code to get these feature specs passing. There’s technically enough in place at this point to allow one to fly off into computerThe Futurecomputer on train2Rails of Glorypray or something, BDDing straight to the top of mount_fujiMount Conversion Ratesunrise_over_mountains. You may want to tackle this as a personal challenge, out of impatience, or for any number of reasons, really. Otherwise, drop by again in the next week or two to see everything come together.