TDD Workflow (Sinatra / Haml / jQuery) Part 1
Introduction
This is a draft. Worth continuing the series? Let me know.
Critter4Us is a webapp used to schedule teaching animals at the University of Illinois Vet School. Its original version was a Ruby/Sinatra application with a Cappuccino front end. Cappuccino lets you write desktop-like webapps using a framework modeled after Apple’s Cocoa. I chose it for two reasons: it made it easy to test front-end code headlessly (which was harder back then than it is now), and it let me reuse my RubyCocoa experience.
Earlier this year, I decided it was time for another bout of experimentation. I decided to switch from Cappuccino to jQuery, Coffeescript, Haml because I thought they were technologies I should know and because they’d force me to develop a new TDD workflow. I’d never gotten comfortable with the testing—or for that matter, any of the design—of “traditional” front-end webapp code and the parts of the backend from the controller layer up.
I now think I’ve reached a plateau at which I’m temporarily comfortable, so this is a good time to report. Other people might find the approach and the tooling useful. And other people might explain places where my inexperience has led me astray.
The example
Suppose an instructor wants to reserve the horses Genesis and Guicho each Tuesday morning from October 8 through the 29th. For historical reasons, that’s done by making a reservation for the 8th and then making three copies. Here’s what that page looks like today:
(As you can see, I haven’t gotten to making the UI attractive yet.)
After the stop date is chosen, a click of the “Duplicate” button causes three successive Ajax calls to be sent to the server. (That’s a bit gratuitous - I just like the idea of the UI showing each reservation “coming to life” in turn.)
Ideally, Genesis and Guicho will be reserved for each Tuesday. However, it’s possible that one of them has already been reserved for the 22nd. In that case, the reservation is still made, but the animal is dropped from it. The user has to edit that reservation to pick a replacement animal.
Testing the Haml
I usually test in an outside-in, mock-heavy style derived from Growing Object-Oriented Software (GOOS). “Derived”, naturally, means I depart from GOOS in my very first step: I avoid automating end-to-end tests. For my small-scale purposes, I don’t find GUI tests (Watir, Selenium, etc.) worth the price. To be clear: I think about GOOS-style tests, and I execute them—I just don’t automate them.
Having thought of an end-to-end test for a particular user task, the next step is to sketch out a page that supports it. I sketch it out in Haml to be the beginnings of the page above.
My page testing is driven by this observation: the user will use the same page for many different reservations. It has a constant part that’s independent of the reservation, and a part that depends on the particular reservation. I only write automated tests for the variable part. I test the constant part by looking at it, by looking at the generated HTML, and by trying the finished story by hand.
There are three parts of this particular page that vary:
-
There’s a snippet of text that reminds the user what reservation she’s working with.
-
There has to be a starting date used to set up the calendar.
-
There has to be some URI that’s used to
PUT
a new repetition into the system. That URI will look something like/reservations/441/repetitions
.
Here’s the setup for tests to check those three claims:
-
Whenever I create a model object, I usually give it a
random
constructor that builds it with innocuous values unless I give it explicit ones. I’ll explain why I’m giving it only one value below. -
I hate the spooky action at a distance of setting instance variables in a controller in order to have some effect way off in another file written in a different language. I’d much rather have a nice explicit function-call interface and a builder or transformation approach. Most of the world thinks differently, so I concede. However, I do try to come closer to a function-call interface by passing in locals instead of setting instance variables.
-
Passing in a whole
Reservation
model object makes me queasy for reasons Bob Martin has explained. I’m intruding the model into the view rather than passing in just what the view needs. This is a consequence of some amount of laziness after bowing to the peer pressure to use partials. I do make some effort to separate presentation-relevant data from model-relevant objects, as you’ll see later. -
In order not to repeat myself, I make one Ruby file the authority over all URIs and links. Therefore, any URI gets passed into the HAML. In pages like these, which exist to aid some specific user task and have a single URI that they use when the appropriate data has been supplied by the user, I call that single URI the “fulfillment” link.
That given, here’s the test in which I claim that a reservation summary appears on the page:
I use shoulda because I like nested contexts and strings to name tests, but I don’t need the rest of Rspec. I use Assert{2.0} because I find Hamcrest-style matchers too wordy and Assert{2.0} gives me usefully detailed failure messages. (It prints every subexpression of a false expression. Wrong is a more recent implementation of this idea.)
The actual reservation summary is created with a partial. By checking for ‘Dr. Dawn’, this test claims the partial has been “called”. (It’s thus a clumsier and less explicit example of a mockish test.) I’m testing only that here because I want the freedom to what the summary looks like without having to change this test.
The next test claims that a correct Javascript date exists in the HTML (presumably in some Javascript code):
Notice that the check is, again, minimalist. The test isn’t about how the date is used, just that it exists. Peeking ahead, it’s actually used like this:
There’s a gap in the test coverage, in that no automated test checks that this initialization code is correct. Pretty low risk, given that it’s straight-line code that cannot avoid execution whenever the page is loaded. So long as I try this page out once, and remember to try the page again if I change the initialization code, I should be safe.
The final test is this:
assert_text_has_attributes
is part of a thin wrapper on top of the HTML parsing part of Capybara (not the integration testing part). I used to use assert_xhtml, a sweet package, but it’s dead in Ruby 1.9 and I haven’t found another one like it. Let me know if you have one.
The Haml code that passes these tests looks like this:
The <head>
is, annoyingly, generated by another action at a distance, with a layout.haml
file that gets wrapped around the above code. That file uses the third of the “parameters”:
What now?
The static page and the acceptance test I have in mind call for some Javascript. That needs to be tested into existence.