Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 228 lines (166 sloc) 8.878 kb
3160ad9 @steveklabnik write better cukes.
authored
1 ---
2 layout: post
3 title: "Write Better Cukes With the Rel Attribute"
4 date: 2011-12-20 09:22
5 comments: true
6 categories:
7 ---
8
9 The other day, I was working on some Cucumber features for a project, and I
10 discovered a neat technique that helps you to write better Cucumber steps.
11
12 Nobody wants to be [cuking it wrong](http://elabs.se/blog/15-you-re-cuking-it-wrong),
13 but what does that really mean? Here's Jonas' prescription:
14
ba888f5 @steveklabnik {% blockquote %} => >
authored
15 > A step description should never contain regexen, CSS or XPath selectors, any
16 > kind of code or data structure. It should be easily understood just by reading
17 > the description.
3160ad9 @steveklabnik write better cukes.
authored
18
19 Great. Let's [pop the why stack](http://www.theregister.co.uk/2007/06/25/thoughtworks_req_manage/)
20 a few times, shall we?
21
22 **Q1**: Why do we want to have descriptions not use regexen, CSS selectors, or code?<br/>
23 **A1**: To give it a simpler language.
24
25 **Q2**: Why do we want it to be in a simpler language?<br />
26 **A2**: So that it's easily understandable for stakeholders.
27
28 **Q3**: Why do we want it to be easily understandable for stakeholders?<br />
29 **A3**: Because then we can share a [common language](http://domaindrivendesign.org/node/132).
30
31 **Q4**: Why is a common language important?<br />
32 **A4**: A shared language assists in making sure our model matches the desires of our stakeholders.
33
34 **Q5**: Why do we want to match the desires of our stakeholders?<br />
35 **A5**: That's the whole reason we're on this project in the first place!
36
37 Anyway, that's what it's really all about: developing that common language.
38 Cukes should be written in that common language so that we can make sure we're
39 on track. So fine: common language. Awesome. Let's do this.
40
41 ## Write some cukes in common language
42
43 Time to write a cuke:
44
45 ```
46 Scenario: Editing the home page
47 Given I'm logged in as an administrator
48 When I go to the home page
49 And I choose to edit the article
50 And I fill in some content
51 And I save it
52 Then I should see that content
53 ```
54
55 Basic CMS style stuff. I'm not going to argue that this is the best cuke in the
56 world, but it's pretty good. What I want to do is examine some of these steps
57 in more detail. How would you implement these steps?
58
59 ``` ruby
60 When /^I choose to edit the article$/ do
61 pending
62 end
63
64 When /^I fill in some content$/ do
65 pending
66 end
67
68 When /^I save it$/ do
69 pending
70 end
71
72 Then /^I should see that content$/ do
73 pending
74 end
75 ```
76
77 Go ahead. Write them down somewhere. I'll wait.<br /><br /><br /><br /><br /><br />
78
79 ... done yet?
80
81 <br /><br /><br /><br />
82
83 ## Implementing a step
84
85 Done? Okay! Before I show you my implementation, let's talk about this step:
86
87 ``` ruby
88 When /^I choose to edit the article$/
89 ```
90
91 When writing this step, I realized something. When trying to write steps like
92 these, there's a danger in tying them too closely to your specific HTML. It's
93 why many people don't write view tests: they're brittle. I actually like view
94 tests, but that's another blog post. Point is this: we know we're going to
95 follow a link, and we know that we want that link to go somewhere that will
96 let us edit the article. We don't really care _where_ it is in the DOM, just
97 that somewhere, we've got an 'edit article' link. How to pull this off?
98
99 ### First idea: id attribute
100
101 You might be thinking "I'll give it an id attribute!" Here's the problem with
102 that: ids have to be unique, per page. With article editing, that might not be
103 a problem, but it's certainly not a general solution. So that's out.
104
105 ### Second time: class attribute
106
107 "Okay, then just use a class. Your blog sucks." Well, let's check out what
108 [the HTML5 spec says about classes](http://www.w3.org/TR/html5/elements.html#classes).
109
ba888f5 @steveklabnik {% blockquote %} => >
authored
110 > Basically nothing about semantics.
3160ad9 @steveklabnik write better cukes.
authored
111
112 Okay, so that's paraphrased. But still, the spec basically says some stuff
113 about the details of implementing classes, but absolutely nothing about the
114 semantics of a class. In practice, classes are largely used for styling
115 purposes. We don't want to conflate our styling with our data, so overloading
116 class for this purpose might work, but feels kinda wrong.
117
118 ### What about the text?
119
120 We could match on the text of the link. After all, that's what people use to
1a36611 @steveklabnik post -> article
authored
121 determine what links to click on. The link with the text "Edit this article"
122 lets us know that that link will let us edit a article.
3160ad9 @steveklabnik write better cukes.
authored
123
124 Matching on the text is brittle, though. What happens when marketing comes
1a36611 @steveklabnik post -> article
authored
125 through and changes the text to read "Edit my article"? Our tests break. Ugh.
3160ad9 @steveklabnik write better cukes.
authored
126
127 There's got to be a better way. Otherwise, I wouldn't be writing this blog post.
128
129 ### The best way: the rel attribute
130
f8f6618 @steveklabnik getsomere.st -> designinghypermediaapis.com
authored
131 When doing research for [my book on REST](http://designinghypermediaapis.com/), I've been doing
3160ad9 @steveklabnik write better cukes.
authored
132 a lot of digging into various standards documents. And one of the most important
133 attributes from a REST perspective is one that nobody ever talks about or uses:
134 the `rel` attribute. From [the HTML5 spec](http://www.w3.org/TR/html5/links.html#attr-hyperlink-rel):
135
ba888f5 @steveklabnik {% blockquote %} => >
authored
136 > The rel attribute on a and area elements controls what kinds of links the
137 > elements create. The attribue's value must be a set of space-separated tokens.
138 > The allowed keywords and their meanings are defined below.
3160ad9 @steveklabnik write better cukes.
authored
139
140 Below? That's [here](http://www.w3.org/TR/html5/links.html#linkTypes):
141
142
ba888f5 @steveklabnik {% blockquote %} => >
authored
143 > The following table summarizes the link types that are defined by this
144 > specification. This table is non-normative; the actual definitions for the link
145 > types are given in the next few sections.
146 >
147 > alternate: Gives alternate representations of the current document.
148 > author: Gives a link to the current document's author.
149 > bookmark: Gives the permalink for the nearest ancestor section.
3160ad9 @steveklabnik write better cukes.
authored
150
151 Hey now! Seems like we're on to something. There's also [RFC 5988](http://tools.ietf.org/html/rfc5988),
152 "Web Linking". [Section four](http://tools.ietf.org/html/rfc5988#section-4)
153 talks about Link Relation Types:
154
ba888f5 @steveklabnik {% blockquote %} => >
authored
155 > In the simplest case, a link relation type identifies the semantics
156 > of a link. For example, a link with the relation type "copyright"
157 > indicates that the resource identified by the target IRI is a
158 > statement of the copyright terms applying to the current context IRI.
159 >
160 > Link relation types can also be used to indicate that the target
161 > resource has particular attributes, or exhibits particular
162 > behaviours; for example, a "service" link implies that the identified
163 > resource is part of a defined protocol (in this case, a service
164 > description).
3160ad9 @steveklabnik write better cukes.
authored
165
166 Bam! Awesome! This is exactly what we want!
167
168 ## So how do we use rel attributes?
169
f8f6618 @steveklabnik getsomere.st -> designinghypermediaapis.com
authored
170 I'll be going into more depth about these kinds of topics in [my book](http://designinghypermediaapis.com/),
3160ad9 @steveklabnik write better cukes.
authored
171 but here's the TL;DR:
172
173 1. There are a set of official types. Try to use those if they're
174 applicable, but they're quite general, so that's often not the case.
df7878b @steveklabnik adding note about URI requirements
authored
175 2. You can put whatever else you want. Space delineated. *
3160ad9 @steveklabnik write better cukes.
authored
176 3. The best way is to use a URI and then make a resource at that URI
177 that documents the relation's semantics.
178
df7878b @steveklabnik adding note about URI requirements
authored
179 We'll go with option two for now, for simplicity. In a real application, make
180 it a URI.
181
182 (*) Technically, this isn't true. Extension relations are _required_ to be URIs,
183 or something that can be serialized to a URI. Again, details are outside of the
184 scope of this post.
3160ad9 @steveklabnik write better cukes.
authored
185
186 ## Making our link, with semantics.
187
188 Here's what a link with our newly minted relation looks like:
189
190 ``` html
1a36611 @steveklabnik post -> article
authored
191 <a href="/articles/1/edit" rel="edit-article">Edit this article</a>
3160ad9 @steveklabnik write better cukes.
authored
192 ```
193
194 Super simple. Just that one little attribute. Now we can write a step to match:
195
196 ``` ruby
197 When /^I choose to edit the article$/ do
198 find("//a[@rel='edit-article']").click
199 end
200 ```
201
202 This code matches what we'd do as a person really well. "Find the link that
203 edits an article, and click on it." We've not only made the title of our step
204 match our idea of what a person would do, but the code has followed suit.
205 Awesome. We can move this link anywhere on the page, our test doesn't break.
206 We can change the text of the link, and our test doesn't break. So cool.
207
7b6489f @steveklabnik adding note about data attributes
authored
208 ## What about stuff that's not links? What about data attributes?
209
f8f6618 @steveklabnik getsomere.st -> designinghypermediaapis.com
authored
210 That's what [my book](http://designinghypermediaapis.com/) is going to talk about, sorry.
7b6489f @steveklabnik adding note about data attributes
authored
211 These kinds of practical examples are one of the reasons I decided to write
212 it in the first place, and I don't want to publish all the content on my
213 blog...
214
3160ad9 @steveklabnik write better cukes.
authored
215 ## Better tests through web standards
216
217 Turns out that diving around in standards has some practical benefits after all,
218 eh? Think about the relationship between your cukes, your tests, and your API
219 clients: Cucumber, through Selenium, is an automated agent that interacts with
220 your web service. API clients are automated agents that interact with your web
221 service. Hmmmm...
222
f8f6618 @steveklabnik getsomere.st -> designinghypermediaapis.com
authored
223 If you want to know more about this, that's what [my book](http://designinghypermediaapis.com/)
3160ad9 @steveklabnik write better cukes.
authored
224 is for. I'll be covering topics like this in depth, and explaining standards in
225 simple language.
226
f8f6618 @steveklabnik getsomere.st -> designinghypermediaapis.com
authored
227 Seriously. Did you sign up for [my book](http://designinghypermediaapis.com/) yet? ;)
Something went wrong with that request. Please try again.