Skip to content

Instantly share code, notes, and snippets.

@h0rs3r4dish
Created April 13, 2012 20:17
Show Gist options
  • Save h0rs3r4dish/2379843 to your computer and use it in GitHub Desktop.
Save h0rs3r4dish/2379843 to your computer and use it in GitHub Desktop.
Inline Ruby tests
$ ruby inline-test.rb demo.rb
demo.rb
my_function ..
first ..
Foo#bar .
Bar::Baz#bar ..
7/7 tests passed in 0.113 seconds
$
=begin
This is a demo of how inline testing works. It's really simple. Everything that gets
tested has to be specified via comment blocks tagged with 'test' before the thing that
it describes:
=begin test
# Tests here
=end
def my_method ...
To describe an outcome, just use parentheses to specify the method's arguments in Ruby
code:
fn() # no arguments
fn(1, 2, "blah") # some arguments
...And then use != or == to specify what it should/shouldn't be:
fn() == 5
fn(10) != 6
If you have classes, you can test those just fine. Pre-test setup conditions are pretty
basic, basically creating an instance using any given arguments via the "setup" keyword.
Multiple setup conditions means that every test for that class will be run with every
type of instance. In the example below, the Baz class has one test specified, but two
constructors, so the test is run twice.
new() # No arguments to Foo.new
new(10) # Now we're cooking
=end
=begin test
fn() == 5
fn(10) == 10
=end
def my_function(val=5)
return val
end
=begin test
fn([0,1,2]) == 0
fn([1,1,1]) != 0
=end
def first(array)
return array.first
end
=begin test
new(1)
=end
class Foo
def initialize(var)
@bar = var
end
=begin test
fn() == 1
=end
def bar
@bar
end
end
module Bar
=begin test
new()
new(1)
=end
class Baz
def initialize(v=5)
@bar = 10
end
=begin test
fn() == 10
=end
def bar
@bar
end
end
end
ARGV.each { |filename|
require_relative filename
print filename
$context = {
:method => "", # current method name
:class_name => "", # class name
:class_init => [ ], # class constructors
:class_last_line => 0, # where the class ends
:ns_name => "", # namespace name
:ns_last_line => 0 # namespace's end
}
$tests = { :passed => 0, :total => 0, :time => Time.now }
lines = IO.readlines(filename)
lines.each_with_index { |line, i|
if line.strip =~ /^module (.+)$/ then
$context[:ns_name] = $1
ends_needed = 1
current_index = i
until ends_needed == 0 do
current_index += 1
current_line = lines[current_index].strip
ends_needed += 1 if current_line =~ /( do|^def|^class| then|[^=]begin)/
ends_needed -= 1 if current_line == "end"
end
$context[:ns_last_line] = current_index
end
next unless line.strip == "=begin test"
test_start_index = i+1
test_end_index = i+2
test_end_index += 1 until lines[test_end_index].strip == "=end"
in_class = (test_end_index < $context[:class_last_line])
in_ns = (test_end_index < $context[:ns_last_line])
next_line_a = lines[test_end_index+1].strip.scan(/^(def|class|namespace) ([^ ]*)/).flatten
case next_line_a.first
when "def"
$context[:method] = next_line_a.last.split('(').first
print "\n" + ((in_ns) ? ($context[:ns_name] + "::") : "") +
((in_class) ? ($context[:class_name] + "#") : "") + $context[:method] + " "
when "class"
$context[:class_name] = next_line_a.last
$context[:class_init] = Array.new
when "namespace"
$context[:ns_name] = next_line_a.last
end
relavent_lines = lines[test_start_index...test_end_index] # exclude the last, "=end" line
relavent_lines.each { |test_line|
if test_line =~ /^new\((.+)\)$/ then
$context[:class_init].push "(%s)" % $1
ends_needed = 1
current_line = test_end_index
while ends_needed > 0 do
current_line += 1
line = lines[current_line]
ends_needed += 1 if line =~ /( then| do|def |[^=]begin)/
ends_needed -= 1 if line.strip == "end"
end
$context[:class_last_line] = current_line
elsif test_line =~ /^fn(\(.*\)) ([=!]=) (.*)$/ then
test = {
:actual => Array.new,
:expected => (eval $3),
:inverse => ($2 == "!=")
}
method_args = $1
if in_class then
$context[:class_init].each { |init|
obj = eval( ((in_ns) ? $context[:ns_name] + "::" : "" ) +
$context[:class_name] + ".new" + init )
test[:actual].push( obj.instance_eval $context[:method] + method_args )
}
else
test[:actual].push( eval $context[:method] + method_args)
end
test[:actual].each { |actual|
assertion = actual == test[:expected]
assertion = !assertion if test[:inverse]
if assertion then
print '.'
$tests[:passed] += 1
else
print 'f'
end
$tests[:total] += 1
}
end
}
}
puts "\n%d/%d tests passed in %0.3f seconds" % [$tests[:passed], $tests[:total],
Time.now - $tests[:time]]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment