Introducing Micronaut, a Lightweight BDD Framework (Or, Reinventing the Bestest Wheel Yet)

Relevance is releasing a new Behavior Driven Development (BDD) framework, even though the Ruby community already has great tools like RSpec, Shoulda, and Context. Let me tell you why.

When I joined Relevance in 2007, I led a conversion from test/unit to test/spec. It was an easy win - we could use "expected.should == actual" syntax, which was nice, and using strings instead of "test_foo_bar" methods is huge. We wrote spec-converter to ease the conversion, went from test/unit to test/spec, and everyone was happy.

Enter 2008: RSpec gains momentum and we see its API stabilize; meanwhile we continue making little tweaks and hacks to our fork of test/spec. I added focused spec support, which basically means you change your "it" to a "fit", and that spec will run in isolation in your suite. This was a major deal for staying in flow with large and/or slow suites - if you have 15 broken specs, focus on the simplest one and make it pass, pick the next failing spec, lather, rinse, repeat. Once you are used to having focused specs, it becomes hard to go without them.

Chad Humphries joined Relevance in mid-2008, and brought with him a passion for RSpec. We had some "differences of opinion" when it came to RSpec vs. test/spec. I wanted support for focused tests and a small codebase that lent itself to extension; Chad wanted things that "just worked" from RSpec and the community of tools and knowledge that exists for it. At the time, test/spec also had some quirks that RSpec had already solved, like before blocks not inherited for nested describes, and better Rails support.

So Chad went off into a desert around Thanksgiving of last year and came back with Micronaut written on stone tablets in about a week. It was lean, mean, API-compatible with RSpec, and well under 2000 lines of implementation code. It can also be extended very easily to support focused specs, or slow specs, or ignored specs, or any other kind of metadata you would ever want in your suite. Each example (an "it" block, aka a spec) and each behavior (a describe block) accepts metadata as an options hash, which you can use to do just about anything you want -- filter what runs at runtime, tweak which modules get included/extended, or change the before/after blocks that evaluate.

A short example:

  gem 'spicycode-micronaut'
  require 'micronaut'

  Micronaut.configure do |config|
    config.filter_run :focused => true
    config.alias_example_to :fit, :focused => true  

  describe "Metadata support in Micronaut" do
    it "this will definitely run", :focused => true do
      self.running_example.metadata[:focused].should == true

    it "this never runs" do
      raise "shouldn't run while other specs are focused"

    fit "this is also focused via the fit alias" do
      true.should_not == false

    it "this won't run, its pending", :pending => true do

    it "this is also pending, with the no block style"

    it "this spec takes *real* long and should only run when we want to take the hit of slow specs", :speed => "slow" do
      2+2.should == 4

Micronaut's metadata system is the next logical step in testing tools. Focused specs in test/spec was a small, fairly ugly, pragmatic hack. Micronaut makes metadata a first class citizen. True metadata becomes really important when you get into more significant codebases. You want this sort of power at the smallest level of granularity in your suite -- the individual example - as well as at higher levels. In Micronaut, metadata at the higher level of a describe block collapse down to nested describes and examples within describes, with lower level metadata always "winning" in the merge.

Micronaut is designed to be easily hackable and extendable, while staying small enough that you can easily find the right extension points and modules you want to change. It has made my day-to-day dev experience more effective and more enjoyable, because I really want to be able to own a tool so fundamental to my daily work as my testing tool. Although Micronaut is still pretty young and the internals are still evolving, the external API should remain fairly stable. Micronaut has been in real-world, production use on at least three Relevance client applications, many of our open source tools, and on the RunCodeRun codebase.

It is important to point out that we really like RSpec and continue to use it and support it. The state of BDD tools in the Ruby world owes a lot to David Chelimsky and team for their work on RSpec, and we love seeing the RSpec codebase get leaner and meaner as components get extracted out and Ruby 1.9 support matures. We also think tools like Shoulda, Bacon, Context are all good, because choice is a "good thing" for the entire Ruby open source community.

If you deal with larger codebases, or struggle with staying in flow in autotest, or just want to try a lean and mean BDD library, give Micronaut a try. The quick and easy way to try it is via the GitHub gem:

  gem install spicycode-micronaut --source

Then drop the above example into a file to start playing. You can also check out the codes on GitHub, file feature ideas and bugs on Lighthouse, verify the build passes on RunCodeRun, and above all let us know what you think!