Aug 27 2014

How You Can Implement Living Documentation (Part 3 in a Series)

MojoTech Developer David Leal continues his series on Living Documentation. Be sure to check out The Why and The How.

In my previous article, we discussed how we should write living documentation. In this article, we’ll explore how we can write the code underlying the documentation. As always, I’ll use Cucumber for the examples, but I believe these principles apply independent of the tool you use.

We previously saw that living documentation mainly describes two different areas of the system:

  • The system behavior--how the system interacts (changes or uses information) with the business domain.
  • The system domain--the system’s entities, and the properties that make up their essence.

In this article, we’ll see how we can write tests for system behavior. We’ll explore domain entity tests in a future article.

Behavioral testing

Consider the following scenario from my previous article:

Scenario: letting clients view photos
When a client views a completed photo shoot
Then they should see the list of all its photos<Paste>

And here’s a possible implementation, written as an end-to-end test:

When(/^a client views a completed photo shoot$/) do
@photo_shoot = db.create_photo_shoot(:shot_at => Time.now,
:photos => [db.build_photo])
client = @photo_shoot.client
visit new_session_path
fill_in "email", :with => client.email
fill_in "password", :with => client.password
click_button "Submit"
visit photo_shoot_path(@photo_shoot)
end
Then(/^they should see the list of all its photos$/) do
@photo_shoot.photos.each do |e|
expect(page).to have_selector(".photo .name", text: e.name)
end
end

We have a feature file, and a corresponding step definition file. I seldom write reusable steps, because step definitions make for lousy reusable units (Ruby already provides us all the reuse facilities we need, thankyouverymuch). So, we don’t have to worry about putting the steps in a file with a name that’s general enough (often several different file names would make sense, making the steps harder to find).

But there are a few problems with the way these steps have been implemented:

First, we are making some assumptions about what a completed photo shoot means. In my previous article, I explained that a photo shoot is considered “completed” when it has a date of shooting and contains at least one photo. Here, we’re not making use of the “completed” abstraction--we’re hard-coding its meaning.

Second, almost nothing is being reused. For example, there’s no way to use the sign in code in other features, if we need to.

Third, the first step definition is big! Steps definitions are somewhat awkward to work with, so they should be kept small.

If at first you don’t succeed...

It’s easy to fix most of the problems discussed above. A second iteration could look like this:

When(/^a client views a completed photo shoot$/) do
@photo_shoot = db.create_completed_photo_shoot
sign_in @photo_shoot.client
visit photo_shoot_path(@photo_shoot)
end
Then(/^they should see the list of all its photos$/) do
@photo_shoot.photos.each do |e|
expect(page).to have_selector(".photo .name", text: e.name)
end
end
module SessionActions
def sign_in(client)
visit new_session_path
fill_in "email", :with => @client.email
fill_in "password", :with => @client.password
click_button "Submit"
end
end

This is better. We abstracted what it means to have a completed photo shoot, by introducing a

create_completed_photo_shoot
factory method for the photo shoot, and we also abstracted what it means to sign in. If you have a simple app, this may well be enough.

Unfortunately, most apps aren’t simple.

When the going gets tough...

As an application’s complexity increases, test implementation becomes trickier. Consider:

  • Different kinds of users use different parts of the app.
  • The same user uses different parts of the app, depending on their goals.
  • Different kinds of users may need to use the same part of the app in slightly different ways.

To deal with these complexities, I created the Dill library. It’s very much a work in progress, so it has a few rough parts, but it’s been helping me deal with the complexities of UI testing (using Cucumber and Rails) in a fairly structured way.

We usually think of people using our app, whatever they do, as simple users, but users put on different roles depending on the goals they have at the moment. For example, a user trying to change their email or password is currently putting on the role of an account manager. A user browsing your store is acting as a prospect, and once they checkout, they become a client.

To achieve their goals, roles need to perform certain meaningful business actions. For example, change password, checkout, or browse product list are all actions a user may perform while putting on different roles. To accomplish an action, a role may start by visiting a certain page, then clicking a link, or submitting a form with some information.

To effectively accomplish anything, actions manipulate widgets. Widgets are declarative wrappers around HTML elements. Dill gives you a DSL that lets you declare and interact with widgets in a fluent manner.

The following code is an annotated example of how we’d implement the step definitions using Dill:

When(/^a client views a completed photo shoot$/) do
@photo_shoot = db.create_completed_photo_shoot
roles.client.
sign_in(@photo_shoot.client).
view_photo_shoot @photo_shoot
end
Then(/^they should see the list of all its photos$/) do
expect(roles.client).to see :photos, @photo_shoot.photos
end
module Roles
def roles
@roles ||= OpenStruct.new(:client => Client.new)
end
end
module Roles
class Client < Dill::Role
# This is a quick way to define a form widget.
form :sign_in, "#new_session" do
text_field :email, "email"
text_field :password, "password"
end
# The #sign_in action, as its name indicates, signs a client in.
# Note how the DSL allows you to express yourself in terms of the
# (UI) domain.
def sign_in(user)
visit new_session_path
submit :sign_in, :email => user.email,
:password => user.password
# we return the role so we can chain method calls. Sometimes,
# you may want to return a *different* role instead.
self
end
# Here, we declare a widget of type List. There is no #list
# helper yet, but it’s in the plans
widget :photos, '#photos', Dill::List do
item '.photo .name'
end
def view_photo_shoot(photo_shoot)
visit photo_shoot_path photo_shoot
self
end
# Used by the #see matcher (see the step definition above).
def see_photos?(photos)
# #value converts the widget into a ruby data structure. By
# default, a List return an array of strings containing the
# text of each of its items. This is usually what you want, but
# you can override a widget’s value by defining your own #value
# method inside the widget.
value(:photos).sort == photos.map(&:name).sort
end
end
end

As you can see, Dill tries to help you write code that is as declarative as possible. We visit a location, submit a form, check the value of a widget, without worrying much about implementation details. UI interactions provided by Dill, like

visit <location>
, or
submit <form>
, let you write the code as you would talk about it, making it slightly more readable (but don’t worry, you’re free to use the standard OO form as well).

Additionally, Dill helps you structure your test code. You just need to find the appropriate role, or maybe create one. If you want roles to share some actions, just create a module and include it in the relevant roles.

Finally, step definitions are still small and very readable. The first layer describes how the domain is setup, what each role does and observes (some scenarios will also check the state of the domain, although that’s not shown in this case). The second layer describes the UI steps taken, while still keeping it abstracted.

End-to-end is not the end

You should now have the tools to write better structured, more manageable, end-to-end tests in Ruby, using Cucumber. If you want to find out more about Dill, you can head on to the GitHub page.

MojoTech

Share: