Articles

Decoupling feature specs from markup
Simple guidelines for resilient tests

Typical scenario

Consider this HTML fragment:

<section id="recipes">
  <h1>The tasty recipes of uncle Alfred <button class="close">Close</button></h1>
  <div id="toad-recipe" class="recipe">
    <h2>Toad lasagna <button class="add">Add to favorites</button></h2>
    <p>First, you have to catch a big, juicy toad.</p>
  </div>
</section>
```html

And this css:

```css
section#recipes button.close{
  /* some funky styles */
}

And javascript (jQuery):

$("#recipes > h1 > button.close").click(function(){
  /* some funky behavior */
});

And this example step definition in your tests:

click_on("div#toad-recipe > h2 > button.add")
# some funky testing

When it goes bad

Now, suppose that you want to change the design of your recipes page, you’ll be very likely to break some javascript functionality or your test. Can you spot the problems generated by the example above ?

Using the same attribute(s) for different concerns (using the class for styles, javascript and testing) will make you lose time and energy each time you’ll want to change one of these, it makes your code rigid.

In addition to that, all of these concerns are way too much dependent on the HTML structure. Should you want to change the h2 for an h3, and you’ll have to fix everything everywhere.

The solution: a clear separation of concerns

Here are my guidelines for manageable html, css and javascript in a well-tested project, I’m still experimenting with this and I’m open to any suggestion or critic.

  • For css: use class attributes only
  • For javascript: use IDs for unique elements or data-attributes for generic elements
  • For testing: use data-attributes only, except when you’re targeting a specific object from the domain, in which case you’re allowed to use the ID.

The refactored HTML fragment:

<section id="recipes" class="recipes" data-purpose="recipes-list">
  <h1>
    The tasty recipes of uncle Alfred
    <button class="close" data-purpose="close-button">Close</button>
  </h1>
  <div id="toad-recipe" class="recipe" data-purpose="entry">
    <h2>
      Toad lasagna
      <button class="add" data-purpose="add-button">Add to favorites</button>
    </h2>
    <p>First, you have to catch a big, juicy toad.</p>
  </div>
</section>

And css:

.recipe .add {
  /* some funky styles */
}
$("[data-purpose='add-button']").click(function(){
  // some funky javascript
});

And step definition:

within( "[data-purpose='recipes-list']" )
  click_on( "[data-purpose='add-button']" )
end
# some funky testing

Benefit

With this approach, the different concerns (js, css, testing) are decoupled from each other.

By reserving the usage of css classes for styling only, the ids for javascript and the data-attributes for javascript & testing, it means that you can completely redesign/refactor/rework one of these concern without affecting the others.

On a large project, it’s a life saver.

Some more tips

For css:

  • You should really never use ids to style elements, find a way to tag the specific element with a meaningful class instead.

For javascript:

  • Instead of writing js code for specific cases, write generic code and use data-attributes instead, like Twitter Bootstrap does.
  • Use ids only to target a unique element corresponding to real objects in the domain logic, ex: #post51.

For tests:

  • Use data attributes instead of classes to describe the purpose of elements
  • With links: use the rel attribute to describe its purpose, as described by Steve Klabnik on his blog:
<a href="/articles/1/edit" rel="edit-article">Edit this article</a>
When /^I choose to edit the article$/ do
  find("//a[@rel='edit-article']").click
end
Posted February 12, 2013
Blog comments powered by Disqus.
author Daniel Reszka
Crafted with by Daniel Reszka who lives and works in Berlin building useful things. You should follow him on social media.