The other day, I was working on some Cucumber features for a project, and I discovered a neat technique that helps you to write better Cucumber steps.
Nobody wants to be cuking it wrong, but what does that really mean? Here's Jonas' prescription:
A step description should never contain regexen, CSS or XPath selectors, any kind of code or data structure. It should be easily understood just by reading the description.
Great. Let's pop the why stack a few times, shall we?
Q1: Why do we want to have descriptions not use regexen, CSS selectors, or code?
A1: To give it a simpler language.
Q2: Why do we want it to be in a simpler language?
A2: So that it's easily understandable for stakeholders.
Q3: Why do we want it to be easily understandable for stakeholders?
A3: Because then we can share a common language.
Q4: Why is a common language important?
A4: A shared language assists in making sure our model matches the desires of our stakeholders.
Q5: Why do we want to match the desires of our stakeholders?
A5: That's the whole reason we're on this project in the first place!
Anyway, that's what it's really all about: developing that common language. Cukes should be written in that common language so that we can make sure we're on track. So fine: common language. Awesome. Let's do this.
Time to write a cuke:
Scenario: Editing the home page Given I'm logged in as an administrator When I go to the home page And I choose to edit the article And I fill in some content And I save it Then I should see that content
Basic CMS style stuff. I'm not going to argue that this is the best cuke in the world, but it's pretty good. What I want to do is examine some of these steps in more detail. How would you implement these steps?
When /^I choose to edit the article$/ do pending end When /^I fill in some content$/ do pending end When /^I save it$/ do pending end Then /^I should see that content$/ do pending end
Go ahead. Write them down somewhere. I'll wait.
... done yet?
Done? Okay! Before I show you my implementation, let's talk about this step:
When /^I choose to edit the article$/
When writing this step, I realized something. When trying to write steps like these, there's a danger in tying them too closely to your specific HTML. It's why many people don't write view tests: they're brittle. I actually like view tests, but that's another blog post. Point is this: we know we're going to follow a link, and we know that we want that link to go somewhere that will let us edit the article. We don't really care where it is in the DOM, just that somewhere, we've got an 'edit article' link. How to pull this off?
You might be thinking "I'll give it an id attribute!" Here's the problem with that: ids have to be unique, per page. With article editing, that might not be a problem, but it's certainly not a general solution. So that's out.
"Okay, then just use a class. Your blog sucks." Well, let's check out what the HTML5 spec says about classes.
Basically nothing about semantics.
Okay, so that's paraphrased. But still, the spec basically says some stuff about the details of implementing classes, but absolutely nothing about the semantics of a class. In practice, classes are largely used for styling purposes. We don't want to conflate our styling with our data, so overloading class for this purpose might work, but feels kinda wrong.
We could match on the text of the link. After all, that's what people use to determine what links to click on. The link with the text "Edit this article" lets us know that that link will let us edit a article.
Matching on the text is brittle, though. What happens when marketing comes through and changes the text to read "Edit my article"? Our tests break. Ugh.
There's got to be a better way. Otherwise, I wouldn't be writing this blog post.
When doing research for my book on REST, I've been doing
a lot of digging into various standards documents. And one of the most important
attributes from a REST perspective is one that nobody ever talks about or uses:
rel attribute. From the HTML5 spec:
The rel attribute on a and area elements controls what kinds of links the elements create. The attribue's value must be a set of space-separated tokens. The allowed keywords and their meanings are defined below.
Below? That's here:
The following table summarizes the link types that are defined by this specification. This table is non-normative; the actual definitions for the link types are given in the next few sections.
alternate: Gives alternate representations of the current document. author: Gives a link to the current document's author. bookmark: Gives the permalink for the nearest ancestor section.
In the simplest case, a link relation type identifies the semantics of a link. For example, a link with the relation type "copyright" indicates that the resource identified by the target IRI is a statement of the copyright terms applying to the current context IRI.
Link relation types can also be used to indicate that the target resource has particular attributes, or exhibits particular behaviours; for example, a "service" link implies that the identified resource is part of a defined protocol (in this case, a service description).
Bam! Awesome! This is exactly what we want!
I'll be going into more depth about these kinds of topics in my book, but here's the TL;DR:
We'll go with option two for now, for simplicity. In a real application, make it a URI.
(*) Technically, this isn't true. Extension relations are required to be URIs, or something that can be serialized to a URI. Again, details are outside of the scope of this post.
Here's what a link with our newly minted relation looks like:
<a href="/articles/1/edit" rel="edit-article">Edit this article</a>
Super simple. Just that one little attribute. Now we can write a step to match:
When /^I choose to edit the article$/ do find("//a[@rel='edit-article']").click end
This code matches what we'd do as a person really well. "Find the link that edits an article, and click on it." We've not only made the title of our step match our idea of what a person would do, but the code has followed suit. Awesome. We can move this link anywhere on the page, our test doesn't break. We can change the text of the link, and our test doesn't break. So cool.
That's what my book is going to talk about, sorry. These kinds of practical examples are one of the reasons I decided to write it in the first place, and I don't want to publish all the content on my blog...
Turns out that diving around in standards has some practical benefits after all, eh? Think about the relationship between your cukes, your tests, and your API clients: Cucumber, through Selenium, is an automated agent that interacts with your web service. API clients are automated agents that interact with your web service. Hmmmm...
If you want to know more about this, that's what my book is for. I'll be covering topics like this in depth, and explaining standards in simple language.
Seriously. Did you sign up for my book yet? ;)