Introducing: MicroTest

Posted by Mathew Abonyi Wed, 10 Jan 2007 22:04:32 GMT

Something I cooked up in an hour for bootstrap testing a testing framework.

If anyone actually wants me to maintain it, just give a shout and I’ll gemify it.



# A super-small testing suite.
# Just two ways to assert:
#   expect { (whatever must return true) }
#   do_not_expect { (whatever must return false) }
#
# A microtest looks like this:
#
#   class BinaryMicroTest < MicroTest
#     def should_exit_cleanly
#       expect do
#         `ruby #{myexec}`
#         $? == 0
#       end
#     end
#   end
#
class MicroTest

  class MicroFailure < StandardError; end

  class << self

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

    def run
      init
      execute
      report
    end

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

    def harvest
      plan = []
      ObjectSpace.each_object(Class) do |k|
        if k.respond_to?(:superclass) && k.superclass == MicroTest
          plan << [k, k.instance_methods.grep(/^should_/)]
        end
      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|
          instance = klass.new
          r = catch_failures(klass, test) { instance.send(test) }
          if r
            self.passed += 1
            print "."
            STDOUT.flush
          else
            self.failed += 1
            print "F"
            STDOUT.flush
          end
          self.expectations += instance.expectation_count
          self.executed += 1
        end
      end
    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.\n%d expectations found." %
        [self.planned, self.executed, self.passed, self.failed, self.expectations]
      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

  attr_accessor :expectation_count

  def initialize
    self.expectation_count = 0
  end

  def expect(&block)
    self.expectation_count += 1
    raise MicroFailure, "should be true" unless block.call
  end

  def do_not_expect(&block)
    self.expectation_count += 1
    raise MicroFailure, "should be false" if block.call
  end

end

at_exit do
  MicroTest.run
end

Just dump that in a helper file and require it, as per normal Test::Unit practices.

Posted in ,  | 1 comment | no trackbacks

Comments

  1. robert said 5 months later:

    hi all.

Trackbacks

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

Comments are disabled