MicroTest just got smaller

Posted by Mathew Abonyi Sat, 20 Jan 2007 13:16:02 GMT

My little library (the smallest ever?) for bootstrapping and lightning-fast testing has just become smaller. I changed the interface a little: now everything is dependent on method name! So you still have two tests, one which expects true and the other false, and which one is determined by using ‘def should_’ or ‘def should_not_’. Oh happy days!


# A super-small testing suite based on method names.
# It has only two types of expectations: should and should_not.
# These two expectations begin the method name, after which you can
# place anything you like. A 'should_...' method will expect true as
# the result of calling the method. A 'should_not_...' method will,
# conversely, expect false.
#
# This is MicroTest's self-test:
#
#   TEST_ROOT = File.dirname(__FILE__) + '/..'
#   require TEST_ROOT + '/lib/microtest.rb'
#
#   class TestMicroTest < MicroTest
#     def should_expect_true
#       true
#     end
#
#     def should_not_expect_false
#       false
#     end
#   end
#
# An example:
#
#   class BinaryMicroTest < MicroTest
#     def should_exit_cleanly
#       `ruby #{MYEXEC}`; $? == 0
#     end
#     def should_not_exit_cleanly
#       `ruby #{MYEXEC} -garbage`; $? == 0
#     end
#   end
#
class MicroTest

  class MicroFailure < StandardError; end

  class << self

    attr_accessor :passed, :failed, :executed, :planned, :plan, :failures

    def run
      init
      execute
      report
    end

    def init
      self.passed, self.failed, self.executed = 0, 0, 0
      self.planned, self.plan = harvest
      self.failures = []
    end

    def harvest
      plan = []
      ObjectSpace.each_object(Class) do |k|
        plan << [k, k.instance_methods(false).grep(/^should_/)] if k < MicroTest
      end
      planned = plan.inject(0) { |c, ary| c += ary[1].size; c }
      [planned, plan]
    end

    def execute
      puts "Running #{self.planned} test(s)..."
      self.plan.each do |klass, tests|
        tests.each do |test|
          expectation = (test =~ /^should_not/ ? false : true)
          instance = klass.new
          r = catch_failures(klass, test) { expect(expectation) { instance.send(test) } }
          if r
            self.passed += 1
            print "."
            STDOUT.flush
          else
            self.failed += 1
            print "F"
            STDOUT.flush
          end
          self.executed += 1
        end
      end
    end

    def expect(expected, &block)
      raise MicroFailure, "should be #{expected}" unless block.call == expected
    end

    def catch_failures(klass, test)
      begin
        yield
        true
      rescue Exception => e
        self.failures << [klass, test, e]
        false
      end
    end

    def report
      puts "\n\n"
      puts "%d planned, %d executed, %d passed, %d failed." %
        [self.planned, self.executed, self.passed, self.failed]
      unless self.failures.empty?
        puts "\nFAILURE REPORT\n"
        self.failures.each { |f| report_failure(f) }
      end
    end

    def report_failure(f)
      puts "Suite: #{f[0]}\nTest: #{f[1]}\nException: #{f[2].class}\nMessage: #{f[2].message}"
      puts ("\t" << f[2].backtrace.join("\n\t"))
    end

  end

end

at_exit do
  MicroTest.run
end

Posted in  | no comments | no trackbacks

Comments

Trackbacks

Use the following link to trackback from your own site:
http://www.mathewabonyi.com/articles/trackback/42

Comments are disabled