First Experiences With RSpec/BDD

2 commentsWritten on August 27th, 2010 by
Categories: Ruby, testing

I wanted to write some tests for the EventPublisher Ruby module i've been playing around with, so i figured i'd just use RSpec for it since that appears to be the most popular testing library in the Ruby world. Now, in the .NET world i never really got into the whole BDD thing and i stuck with TDD because i was quite happy with the coverage that it gave me. In Ruby however, due to the whole dynamic environment i think it's more important to test functionality as completely as possible with as little knowledge as possible of implementation details while mocking/stubbing/faking as little as possible. That doesn't mean i wouldn't mock anything in Ruby tests... it just means that i would try to follow my own rules on the subject as much as possible, whereas in the .NET world many of us (myself included) probably go a little overboard with the whole mocking/stubbing/faking thing.

Something to keep in mind for the rest of this post: i did not write my tests first for this thing. I know, i know, test-first is better. I generally prefer to write my tests before my real code as well, but in this case, the EventPublisher code was the result of just some first time Ruby experiments, and since i'm pretty happy with the code i don't want to get rid of it just so i could do it "right" by re-writing it test-first. So these tests were not meant to drive the design, only to verify the correctness of the code. Also note that the tests are not complete yet. More should be added, but i thought i had enough to post here and hopefully collect some feedback from you guys/gals.

When i started with these tests for the EventPublisher module, i instinctively wanted to test on a too technical level, like i often do in .NET. For instance, i wrote a test that proved that when you called the subscribe method, that the passed in method was actually added to the Event instance that the EventPublisher uses. The thing is: if you use the EventPublisher, you never directly use Event instances. So why on earth should i even know about them in my tests, right? After all, they are an implementation detail. I had to switch my reasoning from "is the code doing what i, a software developer, think it should do?" to something along the lines of "what needs to happen when i trigger an event?". For instance, if i trigger an event, all i should care about is that the subscribed methods are called correctly and that they receive their arguments correctly. How that actually happens is something that i probably shouldn't care about at all in these tests.

I eventually ended up with the following:

class Publisher
    include EventPublisher
    event :my_first_event
    event :my_second_event
    
    def trigger_first_event(args)
        trigger :my_first_event, args
    end
    
    def trigger_second_event(arg1, arg2)
        trigger :my_second_event, arg1, arg2
    end
end

describe EventPublisher, ": triggering event" do
    before(:each) do
      @publisher = Publisher.new
    end

    it "should not fail without any subscribers" do
      @publisher.trigger_first_event "testing"
    end

    it "should pass single event arg correctly to subscribed method with one argument" do
        @args = nil
      def my_first_event_handler(args);
            @args = args
        end

        @publisher.subscribe :my_first_event, method(:my_first_event_handler)
        @publisher.trigger_first_event "testing!"
        @args.should == "testing!"
    end
    
    it "should pass multiple event args correctly to subscribed method with multiple arguments" do
        @args2_1, @args2_2 = nil, nil
        def my_second_event_handler(arg1, arg2)
            @args2_1, @args2_2 = arg1, arg2
        end
        
        @publisher.subscribe :my_second_event, method(:my_second_event_handler)
        @publisher.trigger_second_event "second", "event"
        @args2_1.should == "second"
        @args2_2.should == "event"
    end

    it "should pass single event arg correctly to subscribed block with one argument" do
        event_args = nil
        @publisher.subscribe(:my_first_event) { |args| event_args = args }
        @publisher.trigger_first_event "test"
      event_args.should == "test"
    end
    
    it "should pass multiple event args correctly to subscribed block with two arguments" do
      first_arg, second_arg = nil, nil
        @publisher.subscribe(:my_second_event) { |arg1,arg2| first_arg, second_arg = arg1, arg2 }
        @publisher.trigger_second_event "first", "second"
        first_arg.should == "first"
        second_arg.should == "second"
    end
    
    it "should call subscribed method once for each time it was subscribed" do
      @counter1 = 0
        def my_first_event_handler(args)
            @counter1 += 1
        end
        
        2.times { @publisher.subscribe :my_first_event, method(:my_first_event_handler) }
        @publisher.trigger_first_event "test"
        @counter1.should == 2
    end
    
    it "should call all subscribed methods" do
        @counter1, @counter2 = 0, 0
        def handler1(args)
            @counter1 += 1
        end
        
        def handler2(args)
            @counter2 += 1
        end
        
        @publisher.subscribe :my_first_event, method(:handler1)
        @publisher.subscribe :my_first_event, method(:handler2)
        @publisher.trigger_first_event "first_event"
        @counter1.should == 1
        @counter2.should == 1
    end
    
end

There are a couple of things i like about this. For starters, the output of running this code looks like this:

EventPublisher: triggering event should not fail without any subscribers should pass single event arg correctly to subscribed method with one argument should pass multiple event args correctly to subscribed method with multiple arguments should pass single event arg correctly to subscribed block with one argument should pass multiple event args correctly to subscribed block with two arguments should call subscribed method once for each time it was subscribed should call all subscribed methods

Anyone can read that and understand what kind of functionality is supported.

Another big benefit of these tests is that they contain zero knowledge of the actual implementation of the EventPublisher module. They merely initiate its functionality, and verify whether the expected behavior in the given functional context occurred. I could seriously refactor (or even rewrite) the actual EventPublisher code and i wouldn't have to change my tests as long as i don't change the name and arguments of the subscribe and trigger methods.

For now, i'm pretty happy with this style and organization of tests and will probably stick with it for a while in my Ruby coding. Unless one (or some) of you tell me how i can improve it :)

  • Harry Steinhilber

    My only suggestion, and keeping in mind that I am also new to Ruby :) , would be that you can actually nest describe blocks to build up context. So you could actually have something like:

    describe EventPublisher do
    before(:each) do
    end
    describe "without any subscribers" do
    ... more ...
    end
    describe "with one subscriber" do
    before(:each) do
    end
    describe "passing one argument" do
    it "should pass the argument correctly"
    end
    ... more ...
    end
    ... more ...
    end

  • http://davybrion.com Davy Brion

    @Harry

    ah, that’s another interesting alternative

    i’m gonna experiment with that and a few other approaches to see where i end up :)