advertisement

Print

Behavior Driven Development Using Ruby (Part 1)
Pages: 1, 2, 3

But what would we do to test both kinds of equality? This, unfortunately, would require going to the generic assert() method in Test::Unit, but is quite elegant in RSpec:



  describe "meta-test for equality expectations" do

    it "should easily allow testing different kinds of equality" do
      a = MyClass.new
      b = MyClass.new

      a.should == b
      a.should eql(b)
    end

  end

So again, a key reason RSpec is appealing is that it is highly transparent. We've not even gone into areas in which the BDD / TDD line is drawn in the sand, and you can already see the value of explicit specifications.

Even if you are experienced with Test::Unit, consider which approach would be easier to train a new developer on, and also, which would be more likely to encourage correct testing without tons of trial and error or documentation diving.

Pretty Reports with Practical Value

When it comes to using your specifications as a form of executable, self-verifying documentation, the expressiveness of RSpec pays off.

To aid in this, the framework actually provides some additional reports that can be quite helpful. For example, if you want to extract all of your example text while running the tests, you can easily do so.

Here's what it looks like when run against our String spec:

  $ spec -f s string_spec.rb 

  String indexing
  - should return first character's ASCII value for position 0
  - should return the last character's ASCII value for position -1
  - should return the first two characters for the range 0..1
  - should return the last two characters for the range -2..-1
  - should replace the second character with a new substring
  - should become empty when 0..-1 is set to empty string

  Finished in 0.009989 seconds

  6 examples, 0 failures

You can see that, even without viewing the code, this report tells us something helpful about the behaviors we're specifying. I also find this report helpful for spot-checking my specs to make sure they're clearly testing the behaviors I'm interested in. Sometimes, a poorly named example might mean that the example itself wasn't formed as well as it could be. Although this report might not be the rosetta stone for your code, it definitely does give you a nice eagle eye view of what's going on.

You can actually pull rdoc format as well, which allows you to include this report in your online documentation:

$ spec -f r string_spec.rb 
# String indexing
# * should return first character's ASCII value for position 0
# * should return the last character's ASCII value for position -1
# * should return the first two characters for the range 0..1
# * should return the last two characters for the range -2..-1
# * should replace the second character with a new substring
# * should become empty when 0..-1 is set to empty string

What is really beautiful, however, are the HTML reports. I've added a doomed-to-fail example to show where this really shines:

$ spec -f h string_spec.rb

figure

Though this is ultimately eye candy, it does make viewing your spec status a whole lot more pleasant. This philosophy is carried throughout RSpec, which is certainly intentional. The idea is that if it is fun or at least enjoyable to write your specs, you're more likely to write them.

Enough to Get You Started

Though we've only focused on one context so far, string indexing, it is easy to continue adding more as needed. Here is a sample context for string case manipulations:

  describe "String case manipulations" do

    before :each do
      @string = "fOo" 
    end

    it "should be converted to all caps when #upcase is sent" do
      @string.upcase.should == "FOO" 
    end

    it "should start with a capital letter followed by lowercase letters when #capitalize is sent" do
      @string.capitalize.should == "Foo" 
    end

    it "should be converted to all lowercase when #downcase is sent" do
      @string.downcase.should == "foo" 
    end

  end

Notice that each context has its own before :each method, allowing you to configure the object as needed for your examples. When we run the specs to generate the spec docs, you can see how this set of learning specifications is already painting a meaningful portrait of the behavior of Ruby strings.

  $ spec -f s string_spec.rb

  String indexing
  - should return first character's ASCII value for position 0
  - should return the last character's ASCII value for position -1
  - should return the first two characters for the range 0..1
  - should return the last two characters for the range -2..-1
  - should replace the second character with a new substring
  - should become empty when 0..-1 is set to empty string

  String case manipulations
  - should be converted to all caps when #upcase is sent
  - should start with a capital letter followed by lowercase letters when #capitalize is sent
  - should be converted to all lowercase when #downcase is sent

  Finished in 0.012205 seconds

  9 examples, 0 failures

From here, you're welcome to continue this example by fleshing out more of String's methods, or just wait until the next time around where we'll be tackling harder problems.

We've shown here most of what you will need to begin writing meaningful specs for your own projects, but we've not yet gone into the philosophy of BDD or talked heavily about how to use BDD as an integral part of your design process. This kind of discussion is right around the corner, and, with a working knowledge of RSpec as a tool, it will be easy to tackle the more advanced concepts as we encounter them.

If you'd like to get started with RSpec right away, you might have a look at the Test::Unit cheatsheet. This allows you to take your knowledge of Ruby's unit testing framework to learn the syntax for the various RSpec methods. We'll use everything we've discussed here and more in the articles to come, so a little practice will go a long way.

Gregory Brown is a New Haven, CT based Rubyist who spends most of his time on free software projects in Ruby. He is the original author of Ruby Reports.


Return to O'Reillynet.com.