From user stories to robust testing suites

Jamison Ordway
6 min readJun 5, 2018

--

During my time at Turing, I have cultivated a deep appreciation for test driven development. Getting started with the process is no easy thing, especially since I started learning with more cryptic and particular tools such as Minitest. We were focusing on a lot of unit tests at the time, and the next step in the journey was to learn how to write really great feature tests with RSpec and Capybara.

I remember feeling like I had just gotten a handle on testing and just started to remember some things by the time it was time to switch over to RSpec. At that point, I was intimidated all over again. Fortunately, a little bit of practice with Capybara and RSpec is all it takes to level up with TDD.

Tests are great for making initial decisions about how small pieces of code for a project should really work. Design goes hand in hand with the testing process, but if a whole team is working on a project using TDD, how do they stay on the same page and hold a uniform goal?

Enter the user story. User stories are written in the same manner that a feature would be described by a human, and formatted this specific layout:

  • As a User,
  • When I,
  • And,
  • Then.

The first step in this convention is describing who is interacting with the feature. Is it a visitor? A logged in user? An admin?

The second step describes some interactive behavior. Maybe the user clicks on a page, or fills out a form.

“And” is used for interactive behavior with more than one step.

The last step describes the expected behavior from the feature, from the user’s point of view. Here’s an example of everything put together:

Example of a user story for Bike Share

To offer some context about how user stories are implemented, I’ll share a little bit about my current project at Turing. Bike Share is what I have lovingly called a ‘behemoth of a project,’ but realistically, it seems like a small — mid-size assignment for a software developer in the ‘real world.’ I’m on a team of three people, and have a little less than two weeks to implement 38 user stories (all while practicing TDD of course).

The Bike Share site will analyze bike share usage in San Francisco and allow a user to purchase bike accessories via an online store.

So after getting our database and configuration taken care of, the first step is absolutely to write tests. I followed all relevant documentation for the gems I’m using (RSpec, capybara, simplecov, and Shoulda::Matchers). After running bundle install, this is the user story I started with:

“As a visitor, when I visit the stations index, I see all stations (name, dock count, city, installation date). All attributes must be present.”

This test is going to go in a specific directory so that rails knows where to find it and how to run it. The file also needs to end in ‘spec.rb’ in order to run. My app already has a /spec folder, in which I created a ‘features’ folder for all of our feature tests. I also want to organize the feature tests according to what kind of user the feature is for. So, I the file I created is: ‘bike_share/spec/features/visitors/visitors_can_see_stations_index_spec.rb’.

Let’s require our rails helper and get started. ‘rails_helper’ and ‘spec_helper’ have stuff in them like Shoulda::Matchers and other configuration stuff that we won’t get into right now.

Remember that first part of the user story? In this case, it says “As a visitor,”

and that’s all we need for this first part of our test syntax. Let’s specify with a block what kind of user we’re writing tests for.

Let’s not forget the ‘do’ and the ‘end’ on each of these blocks.

I could have written ‘context’ instead of describe here. The only real difference is readability for humans.

In fact, in this next nested block I’m going to use context to specify what the user is doing, just like the “when” portion of our user story.

Isn’t it nice that our test is starting to resemble the user story that it is based off of?

Now we’re going to jump into the expected behavior part of the user story. We want our user to see a list of stations with all of the station information for each. So here’s how the test looks now:

Now if this test fails, it’ll tell me exactly where something went wrong by printing out the strings pictured above.

Great! Now our user story has effectively been translated into RSpec. The actual test goes inside these blocks. Here’s the whole thing:

Yikes, that’s a handful. Lets dissect it a little.

The first thing that I want to point out is that our assertions use local variables instead of hard-coded strings. It’s good practice to assign objects to local variables so that if the content ever needs to change, it can just be changed in one place (rather than changing every single part of the code after have_content).

So the first thing I do to set up my test is assign the station_1 and station_2 variables to objects. The user story told me that all attributes must be present, so I need to make sure that the objects I’m using in my tests reflect this requirement.

In order to check that the index page has the content I expect, I’ll need to navigate to that page. With Capybara in rails, we have access to path helpers according to the routes. That’s a blog post for another time, though. To learn more about path helpers, check out this great post:

Suffice to say that this ‘stations_path’ path helper will take us to the page that we want. We should still check to make sure, though, which brings us to the ‘expect(current_path).to eq(stations_path)’ capybara query.

Once we’re sure we are at the right place within our test, we need to check for the right content according to the user story. All those lines that start with ‘expect(page)’ are doing this with each element of each station! Nice.

Once I’ve implemented these tests, I can actually check with my own two eyes whether or not this is working by using the launchy gem. Assuming I’ve included this in my Gemfile and run bundle install, I can use the ‘save_and_open_page’ command to see what it looks like in my browser.

Perfect! This is exactly what I wanted to see.

Rinse and repeat! This process is a great starting point for any application, as the tests serve the dual purpose of 1) helping determine what should be built and in what order, and 2) staying on track. If the tests are born from the user stories, it’s easier to stay focused and avoid any rabbit-holes regarding additional functionality which isn’t in the user story.

I’d love to hear about your learning journey. What has your experience been like in writing user stories and tests? Leave me a comment or find me on twitter:

https://twitter.com/JamisonOrdway

And if you’d like to check out the finished Bike Share project, check out our repo here on my project partner’s github:

--

--