Thinking and Testing with Executable Specs – Part 3

This post is the third in my series regarding approaching testing when you are using an executable specification type tool. This one will following on directly from the activities done in the first two posts. (I recommend reading part one and part two if you want to follow along, since these really are interconnected threads.)

So now I realize that since I’m logged in I need to be on the Users page since that’s where administrators go to create users.

I already have an “I am on the login page” step and it sounds like what I need now is a “I am on the users page”. So I’ll make my @starting scenario a @wip again. Then I’ll make another scenario in my create_user.feature file (which is in the specs/tests directory):

Scenario: Go to user page
Given I am on the users page

The step for this (which goes in my app_steps.rb file) is fairly simple:

Ruby

1

2

3

Given(/^Iamontheuserspage$/)do

visit"/users"

end

This won’t work by itself, of course, because in order to get to the user page, I have to do the stuff I just worked on: log in. But here’s the thing: let’s say I added all that stuff I worked on to this step as well. In fact, let’s do this:

Ruby

1

2

3

4

5

Given(/^Iamontheuserspage$/)do

step%{Iamontheloginpage}

step%{Iloginas"administrator"with"admin"}

visit"/users"

end

Okay, that will definitely work. But now notice how I can’t as easily reuse my “I am on the users page” step with other steps because those other steps I might use it with may already be handling the “go to login page and log in” actions. But let’s test that out to see if this is even a problem. Let’s take the @wip off of the @staring scenario and put a @wip on the “Go to user page” scenario. Now I’ll modify my @starting scenario’s steps as such:

Ruby

1

2

3

4

When(/^Icreatea"([^"]*)"user$/)do|role|

step%{Iamloggedinasanadministrator}

step%{Iamontheuserspage}

end

All I did here is added my new “I am on the users page” step as part of this step. That seems logical, right? After all, after “I am logged in as an administrator” then “I am on the users page.” So this should be good, right?

Without executing cucumber, see if you can guess what’s going to happen just by looking at the logic we have created so far?

Ah! Do you see the problem? When the “I create a ‘Clinical Administrator’ user” step is run, the “I am logged in as an administrator” step is going to be run first. However it’s also going to be called again from the “I am on the users page” step. Schematically, here’s what would happen:

When I create a “Clinical Administrator” user

Given I am logged in as an administrator

Given I am on the login page

Given I login as “administrator” with “admin”

Given I am on the users page

Given I am on the login page

Given I login as “administrator” with “admin”

See how that works? This kind of thing is actually fairly easy to do so you have to really consider how you want your steps to be executed, particularly if you know you are going to be chaining steps. In fact, some people argue that chaining steps this way is just short of making a demonic pact with dark forces. I personally have found this approach to be useful so far but, who knows, perhaps I’ll still tangle myself up. At this point, however, I like what chaining steps is getting me even if it does require a bit more thought on my part.

So, in this case, I want to put my “I am on the users page” step back the way it was:

Ruby

1

2

3

Given(/^Iamontheuserspage$/)do

visit"/users"

end

Except … actually, I don’t want it quite back the way it was. Notice how I’m just using a visit call again to go directly to a relative URL within the domain. That’s fine for the login screen since it’s pretty much the only way to get to the application initially. However, for the Users page, I really should simulate how the user will go to the page. This means clicking the Users link on the landing page. So I’ll change my step accordingly:

Ruby

1

2

3

Given(/^Iamontheuserspage$/)do

click_link("Users")

end

Capybara does give you different ways to handle various element actions. I could have used the following instead:

find_link("Users").click

You have to find what seems to make the most sense for you given how the markup on your site is structured.

As it turns out, I’m finding I don’t really need that “Go to user page” scenario I put in. I’ll just remove that. To make this clear, I’m going to to remove the “Go to user page” scenario from the create_user.feature file. I’m not removing the step from the app_steps.rb file. After all, the step itself could come in handy. As I continue to look at my scenarios, I really don’t need the “Go to login page” scenario either, so I’ll remove that as well. In fact, come to think of it, the only scenario I need on this create_user.feature specification is my @starting one. So I’ll delete all of the other scenarios. My feature file now looks like this:

This is an example of the back and forth you might find yourself going through as you figure out the ways you want to craft your specifications. What we essentially just did here was a bit of refactoring of the specification. I used some throwaway scenarios to craft the supporting steps I would need but, in the end, I only stuck with the scenario that, at least so far, is the one I want.

I want to make this a short post because while it may not seem very substantive, that bit about refactoring feature files (or test specifications) is quite important. You’ll notice that I started with somewhat of a declarative scenario, created a set of relatively imperative scenarios to build up the logic I needed, kept the steps of those imperative scenarios, but removed the scenarios themselves.

If you think about this from a programming standpoint, what I’ve done is start to craft the interface — the contract, if you will — for my test specifications. The implementation details will be behind the scenes and ready for use but my hope is that I can provide a relatively concise set of forward-facing scenarios that will be as declarative as possible in nature, describing goals and activities in the language of the business domain.

It might be that these posts are an utter waste of time from just about everyone’s perspective but I’ve found there’s a lack of material out there that really helps you think through how to use tools like Cucumber such that you think about how you want your tests to look and read and how you go about getting your tests to that point. In any event, in the next post we’ll start creating that user that the @starting scenario has been talking about since the very beginning.

About Jeff Nyman

Anything I put here is an approximation of the truth. You're getting a particular view of myself ... and it's the view I'm choosing to present to you. If you've never met me before in person, please realize I'm not the same in person as I am in writing. That's because I can only put part of myself down into words.
If you have met me before in person then I'd ask you to consider that the view you've formed that way and the view you come to by reading what I say here may, in fact, both be true. I'd advise that you not automatically discard either viewpoint when they conflict or accept either as truth when they agree.