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!

Follow Us

05 March 2012
by Patrick Robertson

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:

@slowFeature: Creating a folder on Box As a user, In order to organize documents on Box, I'd like to be able to create foldersScenario: Creating a folder in the root directory Given I have authenticated with BoxWhen I create a folder named "Walrus Love"Then I should be able to retrieve the "Walrus Love" folderScenario: Create a subfolder on Box Given I have authenticated with BoxAnd the "Walrus Love" folder existsWhen 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.newBOX_AUTH_TOKEN,BOX_AUTH_KEYendWhen/^I create a folder named "([^"]*)"$/do|folder_name|@account.root.createfolder_nameendThen/^I should be able to retrieve the "([^"]*)" folder$/do|folder_name|retrieved_folder=@account.root.at("#{folder_name}/")retrieved_folder.name.should==folder_nameendGiven/^the "([^"]*)" folder exists$/do|folder_name|step%{I create a folder named "#{folder_name}"}endWhen/^I create a folder named "([^"]*)" within "([^"]*)"$/do|subfolder_name,folder_name|parent_folder=@account.root.at("#{folder_name}/")parent_folder.createsubfolder_nameendThen/^"([^"]*)" 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_nameend

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.createfolder_nameendThen/^I should be able to retrieve the "([^"]*)" folder$/do|folder_name|retrieved_folder=DocumentManager::Folder.find_by_namefolder_nameretrieved_folder.name.should==folder_nameendGiven/^the "([^"]*)" folder exists$/do|folder_name|step%{I create a folder named "#{folder_name}"}endWhen/^I create a folder named "([^"]*)" within "([^"]*)"$/do|subfolder_name,folder_name|DocumentManager::Folder.create(subfolder_name,folder_name)endThen/^"([^"]*)" should be a child folder of "([^"]*)"$/do|subfolder_name,folder_name|child_folder=DocumentManager::Folder.find_by_namesubfolder_namechild_folder.parent.should==DocumentManager::Folder.find_by_namefolder_nameend

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.