For my Peddinghaus IDE application, I wanted to practice TDD. Ruby has a great built in unit testing framework (the unit testing framework that Rails uses extensively). Ruby also has some great plugins for testing. For Peddinghaus, I am using Cucumber and RSpec. These two plugins, along with Test::Unit are the basis for my testing stack (more information on this stack later). However, I really didnít have a way to run these tests effectively. So, I set out to create my own Ruby Test Runner.

Rails is synonymous with Ruby in most peopleís eyes. However, Ruby is a powerful language in itself that has an ever-growing library of tools and plugins that is going to make it a first-class language in the near future. My project is a desktop environment application and thus isnít a Rails app. This means that I donít get some of the built-in test runners that are in Rails.

So, you can either run the tests module by module, testing framework by testing framework, or you can create a script. Some people would prefer to make a sh script that can run in their particular environment. This is fine, but I wanted to see if I couldnít hook directly into the APIs of each framework. Getting into the Test::Unit framework was pretty easy and straightforward while hooking into the two plugins was more difficult. In the end for that, I had to settle for a work-around. Code follows:

# runtests.rb
# runtests.rb
# This file will run the Cucumber, RSpec, Integration
# and unit tests. It will also accept arguments. Examples
#
# irb> require 'runtests.rb'
# irb> RunTests::Run
#
# irb> RunTests::Run("cucumber")
#
# irb> RunTests::Run("rspec")
#
# irb> RunTests::Run("integration")
#
# irb> RunTests::Run("unit")

require 'test/unit/ui/console/testrunner.rb'

class RunTests
  def self.Run(arg)

    unit_suite = Test::Unit::TestSuite.new("Unit Tests")
    Dir.entries("unit").each { |file|
      if file.include? ".rb"
        unit_suite << file
      end
    }

    integration_suite = Test::Unit::TestSuite.new("Integration Tests")
    Dir.entries("integration").each { |file|
      if file.include? ".rb"
        integration_suite << file
      end
    }

    if(arg == "cucumber" or arg == "features")
      system("cucumber features/")
    elsif(arg == "rspec" or arg == "spec")
      system("spec specifications/")
    elsif(arg == "integration")
      Test::Unit::UI::Console::TestRunner::new(integration_suite).start
    elsif(arg == "unit")
      Test::Unit::UI::Console::TestRunner::new(unit_suite).start
    else
      print "Running Cucumber..\n"
      system("cucumber features/")
      print "Running RSpec..\n"
      system("spec specifications/")
      Test::Unit::UI::Console::TestRunner::new(integration_suite).start
      Test::Unit::UI::Console::TestRunner::new(unit_suite).start
    end
  end
end
# TestRunner.rb
# Command line test runner for runtests.rb

require 'runtests'

exit = false

while(!exit)
  print " TestRunner > "
  a = gets.gsub("\n", "")

  if(a == "exit")
    exit = true
    print "Exiting..."
    break
  end

  if(a == "help")
    print "Help for TestRunner\n"
    print "=============================================\n"
    print "run (no arguments) -- Runs all tests\n"
    print "run c(ucumber)     -- Run Cucumber tests\n"
    print "run s(pec)         -- Run RSpec tests\n"
    print "run i(ntegration)  -- Run integration tests\n"
    print "run u(nit)         -- Run unit tests\n"
    print "help               -- This\n"
    print "exit               -- Exit the runner\n"
  end

  if(a == "run")
    RunTests::Run("")
  end

  if(a == "run c")
    RunTests::Run("cucumber")
  end

  if(a == "run s")
    RunTests::Run("spec")
  end

  if(a == "run i")
    RunTests::Run("integration")
  end

  if(a == "run u")
    RunTests::Run("unit")
  end
end

I decided to break it up because I wanted to be able to separate the UI and the logic. This is a very simple case that will likely never be changed, but I may be motivated enough to write a GUI for the test runner that replaces (or supplements) the command like test runner. Or, I could change the business layer and never worry about UI layer changes. I think its more clean this way.

(Note: I realize that there are some bad variable names and inconsistencies [notably a and the long form of the arguments for the test runner] but they will be fixed before it goes into source control.)

As you may see, the workaround for Cucumber and RSpec is to just call the commands themselves. However, Iím sure that there is a way to hook into these testing frameworks and create your own runner (and, if not, that should definitely be added). I could imagine that if I were to put a real team on this project and would want some real results, Iíd have someone put some time into an integrated test runner GUI with an integration server test runner counterpart. For now, the above solution should suffice.

If anyone has information on the Cucumber and RSpec runners, please let me know!