The scientific method: learning BDD, RSpec, Ruby, and Rails with language experiments

A body of techniques for investigating phenomena and acquiring new knowledge, as well as for correcting and integrating previous knowledge. It is based on gathering observable, empirical, measurable evidence.

That’s how the venerable Wikipedia defines the scientific method. In one of my former careers, systematic observation (through tests and repeated replication of results) was inherent in our every activity. Yet when I took to learning programming, I somehow forgot that the these methods aren’t just useful for gathering knowledge about the natural world, but about everything—including the digital world.

This realization came to me as a little flash of light while reading my brilliant former instructor Mike Clark’s post titled ‘Are you there world?,’ which suggests testing as a first step in learning Ruby. For better or worse, this is actually the exact inverse of the way most of us learn languages and frameworks. Testing is usually one of those things that we intend to get around to someday but never quite take the time to learn properly—something to do at the nebulous “end” of your big project as a way of making sure things aren’t breaking in production. I had fallen into this trap as well. I learned Ruby mostly by randomly stumbling around, trying new things in attempts to build little sandbox projects. There was nothing systematic about this, nor was there any attention to replication of my results. (Most of it involved lots of browser refreshing.) Somehow, I had forgotten the scientific method as the path to knowledge.

Mike, however, advocates what could certainly be called a scientific approach to learning Ruby, based on what are effectively systematic, repeatable, language experiments. He challenges us to put the computer in charge of remembering all the experimentation we do when we’re first learning a language, by writing tests for all the new functionality in the language that we learn as we’re learning it.

Tests, after all, are statements of an expectation followed by a verification that the actual results of an operation evaluate to that expectation. When we’re first learning, we poke at Ruby with a stick (irb) and see how it reacts. Mike is merely suggesting making these observations systematic and repeatable—recording how the subject reacts for posterity so you’ll be able constantly to review and verify what you’re learning about it. Think of yourself as a scientific pioneer who has discovered a completely new and sophisticated organism (in our case, Ruby). Earn that lab-coat of yours: keep good records of your findings!

I’m going to stretch the concept of “replication” from the scientific method a bit here, but I think it also applies as a metaphor at least. The idea is that when you have language tests in place to record what you’ve learned, you always know when your knowledge becomes invalid. When your expectations fail, you have something new to learn. For example, things are in constant flux in the Rails core code base. In 2.0, old ways of doing things are going to start breaking. If you’ve kept a record from the beginning of everything you know about Rails (in the form of an executable test), all you have to do is update your version of Rails, run the test again, and you’ll know everything that you have to learn over again simply by seeing what fails.

I just have one objection to Mike’s article. His article is all about encouraging people to use Ruby’s built-in Test::Unit framework. As it happens, Test::Unit is the main reason I went so long without ever learning testing in the first place. It’s syntax is weird, ugly, and requires significant mental backflips in order to understand what you’re trying to express. This isn’t what we spoiled young Rubyists are accustomed to. Let’s take a look at a gently modified example from Mike’s article

  def test_length
    string = 'abc'
    assert_equal(3, string.length)
  end

For those of us used to expressions like 2.days.from_now, this reads as utter gibberish: “test length assert equal 3 string equaling ‘abc’ length” That doesn’t make sense in any dialect of Engrish, Franglais, or Spanglish I’m acquainted with. But there is no need to succumb to this aphasia virus; there’s hope.

That hope comes to us in the form of RSpec, the new hotness in the Ruby and Rails testing world. Now, I should preface my discussion of RSpec by warning that most of the people who like to write about RSpec are given to a certain form of intellectual onanism, which forces them to insist on all manner of esoteric precepts and justifications for their framework. Watch out in particular for citings of the Sapir-Whorf Hypothesis, which doesn’t relate to the House of Mogh but rather a unjustifiably baroque way of expressing the simple idea that the the sort of language we use affects the way we think. Put even more simply, it’s better when we express ideas in a way that makes sense.

And that’s really what RSpec is all about: expressing tests in a way that makes sense. Let’s rewrite our Test::Unit example in RSpec terms:

context "A String" do

  specify "Should return the number of characters it contains when sent the message 'length'" do
    string = "abc"
    string.length.should_equal 3
  end

end

How does that read in English? Well, we could run the file from the terminal with spec ruby_spec.rb -cfs and we’d get a nicely formatted answer in the output.

A String

-Should return the number of characters it contains when sent the message ‘length’

(In fact, if you do sudo gem install redgreen, you’ll even get that second line in green if its spec passes, or red if it fails.)

Or, alternatively, just read the code itself, “A string should return the number of characters it contains when sent the message ‘length’: string equaling ‘abc’, length should equal 3.” How does that compare to “test length assert equal 3 string equaling ‘abc’ length” Yes, it’s more verbose, but that’s because it actually expresses an intelligible idea.

Incidentally, this syntax gives us the added advantage of rendering us so pliant and at ease with tests (because they’re not expressed in such a weird backwards-thinking way) that it feels much easier actually to write them before we write the code we want to test. This test-first approach is something many of us believe to be a vastly superior way of doing software design (aka Behavior Driven Development, or BDD, aka Test Driven Development, or TDD). This delay-of-gratification approach to coding normally requires significant restraint and patience, but with RSpec, it just feels right. Expressing tests/specs/expectations (or whatever you want to call them) in an intuitive way makes it easier to think about how you want your application to behave, allows you to specify that behavior, and then test that it meets the specification in what feels like natural language.

As an aside, there are those who, in order to protect the new hotnessness of their respective fiefdoms, will flip their wigs at the mere suggestion that TDD and BDD are similar/interchangeable concepts. You’ll have to forgive their casuistry. They mean well.

Now that I have this whole new, completely intuitive way of expressing and testing expectations in Ruby, I find myself actually writing tests all the time, for everything. Both as a learning tool and as a design tool. Now, I’m working with my pals over at The Edge Case to learn more about its advanced features, like “mocking,” “stubbing” and other tricks to help me avoid those nasty Rails fixtures. So look for more reports from the frontiers of RSpec science here in the future.

Mike Clark is right: the scientific approach is the best way to gather knowledge (and by extension to learn Ruby). There has just been a little paradigm shift in our scientific approach. Give RSpec a try, and see if it doesn’t make that lab-coat fit a little more comfortably.