Join our team!

We're looking for mid-level and senior software developers who love working throughout the stack, and have a track record for designing, building, shipping, and supporting web services and applications. Ideal candidates love Ruby, Rails and Ember, and using BDD and agile processes to ship working software quickly! Experience with Redis, Postgres, RabbitMQ, and ElasticSearch are a huge plus!

Learn more about our mission by visiting IoraHealth.com and by following @IoraHealth.

Learning Test Strategy: BDD the Unknown

Using spikes is a crucial tool in the agile developers arsenal. I’ve noticed that there is often a post-spike depression in a developers speed when they begin to incorporate the concepts from the “throw away” code into the application and they must resume writing tests. Usually when asked why they didn’t test-drive his or her spike a developer will answer “It’s nearly impossible to test code when you have no idea what you’re doing!”.

I’d like to walk you though my approach to spike development that keeps BDD in the loop.

Enter the Learning Test Strategy

The learning test strategy is a pretty simple concept. Instead of just writing code you throw away, you begin by writing acceptance tests for what you’d like to implement during your spike and then write throw away code to satisfy the criteria set forth in the tests. Instead of just having throw away code you now have an integration test suite for the features you are about to develop.

It’s Cucumber, I know this!

The very existence of a spike hints that you have one or more feature stories that implement the systems you are exploring inside your codebase. To me, this sounds an awful lot like acceptance criteria that I can build into executable Cucumber tests during my spike story.

I’ve recently developed a document management library that allows our application to create folders and files on a cloud based document management service. No one on the team had developed against the API before, so we needed to spike against it. We knew that ultimately our system should:

  • Create a folder when a new clinic is added to our system
  • Create a folder relative to the clinic folder for a patient when they are registered

So now I know what I need to ask from the API. I could just start writing code to discover how the API works, or I could write a Cucumber feature instead:

@slow
Feature: Creating a folder on Box
  As a user,
  In order to organize documents on Box,
  I'd like to be able to create folders

  Scenario: Creating a folder in the root directory
    Given I have authenticated with Box
    When I create a folder named "Walrus Love"
    Then I should be able to retrieve the "Walrus Love" folder

  Scenario: Create a subfolder on Box
    Given I have authenticated with Box
    And the "Walrus Love" folder exists
    When I create a folder named "Stop clubbing, seals" within "Walrus Love"
    Then "Stop clubbing, seals" should be a child folder of "Walrus Love"

Do note the @slow tag, this indicates that I’m writing an integration test that interfaces directly with an external library and can take some time and have network connectivity issues. You can eliminate this by using VCR, which I will utilize elsewhere in the test suite, but there is something to be said for having a pure integration test set on a controllable tag. I can skip running the @slow tag while locally developing, but enforce running the test for a production build if I so desire.

Now I can feel free to write some terrible inefficient and hacky code in the step definitions to make these scenarios pass:

require 'box-sdk'

Given /^I have authenticated with Box$/ do
  @account = Box::Account.new BOX_AUTH_TOKEN, BOX_AUTH_KEY
end

When /^I create a folder named "([^"]*)"$/ do |folder_name|
  @account.root.create folder_name
end

Then /^I should be able to retrieve the "([^"]*)" folder$/ do |folder_name|
  retrieved_folder = @account.root.at("#{folder_name}/")

  retrieved_folder.name.should == folder_name
end

Given /^the "([^"]*)" folder exists$/ do |folder_name|
  step %{I create a folder named "#{folder_name}"}
end

When /^I create a folder named "([^"]*)" within "([^"]*)"$/ do |subfolder_name, folder_name|
  parent_folder = @account.root.at("#{folder_name}/")
  parent_folder.create subfolder_name
end

Then /^"([^"]*)" should be a child folder of "([^"]*)"$/ do |subfolder_name, folder_name|
  child_folder = @account.root.at("#{folder_name}/#{subfolder_name}/")

  child_folder.name.should == subfolder_name
end

The tests aren’t DRY, the code isn’t DRY, but now I have an understanding of the API I’m implementing. I can already start to see pain points in the API implementation, and in future stories I can write a more effective wrapper to alleviate them.

Refactoring Your Learning Tests During Feature Development

Once I begin feature development, I need to ensure I clean up my learning tests as I implement a wrapper for the API. This ensures I have a fully functional suite of intergration tests so that I can judiciously mock, stub, and VCR my unit tests where the application is concerned.

Here’s something closer to the actual result:

When /^I create a folder named "([^"]*)"$/ do |folder_name|
  DocumentManager::Folder.create folder_name
end

Then /^I should be able to retrieve the "([^"]*)" folder$/ do |folder_name|
  retrieved_folder = DocumentManager::Folder.find_by_name folder_name

  retrieved_folder.name.should == folder_name
end

Given /^the "([^"]*)" folder exists$/ do |folder_name|
  step %{I create a folder named "#{folder_name}"}
end

When /^I create a folder named "([^"]*)" within "([^"]*)"$/ do |subfolder_name, folder_name|
  DocumentManager::Folder.create(subfolder_name, folder_name)
end

Then /^"([^"]*)" should be a child folder of "([^"]*)"$/ do |subfolder_name, folder_name|
  child_folder = DocumentManager::Folder.find_by_name subfolder_name

  child_folder.parent.should == DocumentManager::Folder.find_by_name folder_name
end

Wrapping it up

It’s often easy to forget that Cucumber is more than just testing how a user will interact with the UI of an application. It can really be a powerful tool to keep you focused on learning exactly what you need when you are in uncharted coding waters. Furthermore, it turns an exercise where you’d typically throw away code into an exercise when you walk away with a basic framework for testing future features. I’ve found this to be extremely valuable when exploring third party API’s and new development techniques.