A couple years ago I wrote a library called AlterEgo. It’s an implementation of the State Pattern for Ruby. I consider the library to be retired at this point—other libraries, such as State Machine and ActiveModel, have since incorporated all of its important features.
Every now and then, though, I still receive unsolicited compliments on the AlterEgo spec suite. Let me say that again: I get compliments specifically for the tests, not for the library itself.
When I sat down to write AlterEgo, I decided to try an experiment. I decided to write “literate specs” for the code—specs that would double as the library’s executable documentation. My workflow went like this:
- Write some introductory verbiage to an AlterEgo feature. Put the text inside a comment block.
- Immediately following the comment, write a spec that clearly demonstratred the feature in idiomatic Ruby and RSpec.
- Update the library so that the spec passed.
- Go back to step 1.
Judging by the responses I’ve received, the experiment was a success. Here’s a snippet from the AlterEgo specs, to give you a feel for the format (note, this was in an older version of RSpec):
# It is possible to get a list of currently handled requests, as well as a list # of all possible requests supported in any state. describe TrafficLightWithRedCountdown do before :each do @it = TrafficLightWithRedCountdown.new end it "should know what requests are supported by states" do @it.all_handled_requests.should include(:cycle, :color, :seconds_till_red) end end
This “literate spec” style was inspired by Donald Knuth’s “Literate Programming” methodology. Knuth’s idea was to approach programming the same way one would approach writing a paper or a book:
Let us change our traditional attitude to the construction of programs: Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do.
The practitioner of literate programming can be regarded as an essayist, whose main concern is with exposition and excellence of style. Such an author, with thesaurus in hand, chooses the names of variables carefully and explains what each variable means. He or she strives for a program that is comprehensible because its concepts have been introduced in an order that is best for human understanding, using a mixture of formal and informal methods that reınforce each other.
Knuth had another explanation for the name he chose, one which has always amused me, and which inspired the title of this article:
I must confess that there may also be a bit of malice in my choice of a title. During the 1970s I was coerced like everybody else into adopting the ideas of structured programming, because I couldn’t bear to be found guilty of writing unstructured programs. Now I have a chance to get even. By coining the phrase “literate programming,” I am imposing a moral commitment on everyone who hears the term; surely nobody wants to admit writing an illiterate program.
By choosing RSpec for the AlterEgo specs, I was deliberately choosing a syntax which would build naturally from the prose descriptions of the functionality being described. Could I have written the same specs using Test::Unit? Certainly. But I daresay I would have been forced to use more in-line comments to explain the particular behavior I was demonstrating. The RSpec, in most cases, reads very similarly to the English explanations I would have otherwise employed.
TDD (and by extension, BDD) is first and foremost a design discipline; it is a tool for thought which enables us to tease out the needed code structure in a very organic way. But there are side-benefits to TDD which we also find very helpful to the software process. The most obvious is the fact that it produces comprehensive regression test suites as a by-product.
Another fringe benefit of TDD which has fallen somewhat by the wayside, in my opinion, is the notion that the tests can double as the documentation—documentation which, by its nature, can never fall out of sync with the source code. This feature of TDD was a Big Deal back when I was first getting into open source; it was very popular to reply “just read the tests” in response to the question “where is the documentation”, and this was a novel change from the heavyweight documentation which had up until then been considered one of the requisites of software development.
I don’t see this as often in Ruby land these days. More often, the documentation consists of the README, a blog post or two, perhaps a screencast, and some Wiki pages on GitHub. I often find that the test/spec suite for a given class is essentially a “bag o’tests”, with no particular organization or attention to readability. The tests presumably served their purpose as part of the design process, and are now doing time as regression checks—but they are in no way ambassadors for the library they describe.
There are exceptions. And more often than not, when I find exceptions, they are written in RSpec or Cucumber, not Test::Unit. This is anecdotal, of course, but it makes sense to me. The choice of RSpec is, at some level, the choice to think about code from the perspective of English prose, rather than from the perspective of code. I can’t say for certain which is cause and which is effect, but in my experience “literate” and RSpec/Cucumber tend to go hand-in-hand.
One of my favorite recent examples is the specs for Myron Marston’s wonderful VCR library, which you can read on Relish. Personally I think those specs should be preserved and enshrined as some sort of international monument to executable tests. They are sublime.
require 'arrayfields' # # the class Array has only a few added method, one is for setting the fields, # when the fields are set for an array THIS INSTANCE ONLY will be modified to # allow keyword access. other arrays will not be affected! # a = [0,1,2] fields = ['zero', 'one', 'two'] a.fields = fields # ONLY the Array 'a' is affected! # # keyword access is now allowed for many methods # p a['zero'] #=> 0 p a['one'] #=> 1 p a['two'] #=> 2 p a.at('one') #=> 1 p a.values_at('zero', 'two') #=> [0, 2]
There is a different, but related, effect of using RSpec. Not only does it seem to encourage a literate test-writing style, but it also tends to encourage slightly more idiomatic Ruby, at least among its more conscientious users.
Here’s a short example of what I’m talking about. Let’s say I have a class “Frood”, which defines a couple of predicates:
class Frood def knows_where_its_towel_is? true end def is_hoopy? true end end
Let’s take a look at the Test::Unit tests for the predicates:
f = Frood.new assert(f.is_hoopy?) assert(f.knows_where_its_towel_is?)
Straightforward enough. Now let’s take a look at the equivalent idiomatic RSpec:
f = Frood.new f.should be_is_hoopy f.should be_knows_where_its_towel_is
BLEAAAARGH!!! That looks awful!
Why does it read so poorly? Because we used some non-idiomatic naming conventions for our predicates. If we rename them to the idiomatic
#aware_of_where_its_towel_is?, respectively, then we get some much better-looking RSpec:
f = Frood.new f.should be_hoopy f.should be_aware_of_where_its_towel_is
This is just one example. There are a lot of little ways that RSpec makes non-idiomatic Ruby code look glaringly obvious. It tends to break, for instance, when you implement
#method_missing without a matching
And I think this is one of the reasons it gets a lot of grief: a lot of people write mildly non-idiomatic code—I know I do, from time to time—and RSpec makes this kind of code look very ugly. It’s similar to the complaints I often see about Mock Objects: in most cases, when I see someone complaining about how elaborate and brittle their mocks are, I find that the code in question is littered with SRP violations. Mocks, and to some degree RSpec, are like the Hydrogen Peroxide of programming: they fizz up where they encounter subtle technical debt.
I know, I know, I can hear what you’re thinking. “I don’t need no RSpec to tell me if my code sucks!”. Maybe you’re right. TDD, BDD, RSpec, Literate Programming—they are all just tools for thought, and if we can think real good we don’t need them.
Practically every hacker seems to go through a phase where he or she reads about JWZ writing Netscape without any Unit Tests and says to him or herself “yeah! tests are overrated! I’m going to be just like JWZ and just code it right!“. The flaw in that plan is that they aren’t JWZ. Neither am I, and statistically speaking, neither are you. There’s a reason that even seasoned airline pilots step through written checklists during preflight. Even masters make use of disciplines, and tools for thought.
The point of this article is not to convince you to use RSpec. If anything, I just want to provoke you to ask yourself some questions about your testing discipline. Are your tests literate? Are you writing test suites that read like manuals to your code? Does the code you are working on warrant tests like that (the answer may be “no”)?. Are you writing example code that looks awkward and forced, or that is flowing and idiomatic? How would your classes and methods change if you were writing an essay on them for publishing in a printed book? Whose eyes are you looking at your tests through—the eyes of the newest member of your team, or your own seasoned eyes?
If you know of more examples of literate specs—whether in RSpec, Test::Unit, Shoulda, or any other form—I’d love to see them! Please leave a link in the comments.