Write better cukes with the rel attribute
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.
Write some cukes in common language
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?
Implementing a step
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?
First idea: id attribute
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.
Second time: class attribute
ā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.
What about the text?
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.
The best way: the rel attribute
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: the 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.
Hey now! Seems like weāre on to something. Thereās also RFC 5988, āWeb Linkingā. Section four talks about Link Relation Types:
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!
So how do we use rel attributes?
Iāll be going into more depth about these kinds of topics in my book, but hereās the TL;DR:
- There are a set of official types. Try to use those if theyāre applicable, but theyāre quite general, so thatās often not the case.
- You can put whatever else you want. Space delineated. *
- The best way is to use a URI and then make a resource at that URI that documents the relationās semantics.
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.
Making our link, with semantics.
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.
What about stuff thatās not links? What about data attributes?
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ā¦
Better tests through web standards
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? ;)