Bil said:
require 'test/unit'
Then code test-first and you'll have executable documentation.
I've coded up test-extract which lets you embed some of your test-cases
cloaked as sample code into your RDoc strings.
It looks like this in practice:
# Creates a Regexp which matches a literal string. In this
# string any special regular expression meta-characters will
# be escaped automatically.
#
# # This creates a Regexp which will match 3 "foo"s.
# re = Regexp::English.literal("foo" * 3)
# re.match("foofoofoo")[0] # => "foofoofoo"
def literal(text); Node::Literal.new(text); end
(Sample from Regexp::English)
# Unfreeze a frozen Object. You will be able to make
# changes to the object again.
#
# obj = "Hello World".freeze
# obj.frozen? # => true
# obj.unfreeze
# obj.frozen? # => false
# obj.sub!("World", "You!")
# obj # => "Hello You!"
def unfreeze
if $SAFE > 0
raise(SecurityError, "Insecure operation `unfreeze' at level #{$SAFE}")
end
return self if direct_value?
self.internal.flags &= ~RubyInternal::FL_FREEZE
return self
end
(Sample from Evil-Ruby)
I've attached it to this email.
Code test-first with a partner, and you'll have /sane/ executable
documentation.
Heh, I have not done this yet, but I've noticed that code design
generally turn out better if you do it via pair programming.
When test/unit2 appears, tests will become even more like documentation.
I'm interested: What will it change?
I've played with Literate Programming for over a decade and found
that it nearly always violates the DRY principal. As a result, it
eventually gets out of sync and begins to lie.
Would you consider the code samples from above Literate Programming?
Regards,
Bil Kleb, Hampton, Virginia
More regards,
Florian Gross
require 'test/unit/testcase'
require 'test/unit/ui/console/testrunner'
# Example isn't another way to teach, it is the only way to teach.
# --Albert Einstein
class Extracter
def self.process(fn)
new(File.read(fn))
end
def initialize(content)
comment_block_re = /((?:^\s*?(?:#.*?)?\n)+)/m
component_re = /\s*(?:class|def|module|alias)\s+:?([^\s()]+)?/
blocks = content.scan(/#{comment_block_re}#{component_re}/)
test_suite = Class.new(Test::Unit::TestCase)
has_test = false
blocks.each do |(comment, component)|
code_in_doc_re = /^(\s*# +(?:.*?)$)/
tests = comment.scan(code_in_doc_re)
body = tests.map do |test|
test.map do |raw_line|
line = raw_line.sub(/^\s*#\s{0,3}/, "")
if md = /(.*?)#\s*(=>|~>|raises?)\s*(.*?)$/.match(line)
new_line, type, result = *md.captures
new_line.strip!
case type
when "=>"
["begin",
" assert_equal(#{result}, #{new_line})",
"rescue => err",
" assert_equal(#{result.inspect}, (#{new_line}).inspect)",
"end"].join("\n")
when "~>", "raise", "raises"
"assert_raises(Object.const_get(#{result.inspect})) { #{new_line} }"
end
else
line
end
end.join("\n")
end.join("\n")
unless component
if $DEBUG
STDERR.puts "Can't get name for this code:",
body.gsub(/(?:\r?\n){2}/, "\n")
end
component = test.hash.abs
end
if body and not body.empty?
has_test = true
test_suite.class_eval %{
def #{test_method_name(component)}
#{body}
end
}
end
end
if not has_test
test_suite.class_eval do
def test_main; end
end
end
Test::Unit::UI::Console::TestRunner.new(test_suite).start
end
def test_method_name(component)
result = "test_#{component}"
{
"+" => "op_plus",
"-" => "op_minus",
"+@" => "op_plus_self",
"-@" => "op_minus_self",
"*" => "op_mul",
"**" => "op_pow",
"/" => "op_div",
"%" => "op_mod",
"<<" => "op_lshift",
">>" => "op_rshift",
"~" => "op_tilde",
"<=>" => "op_cmp",
"<" => "op_lt",
">" => "op_gt",
"==" => "op_equal",
"<=" => "op_lt_eq",
">=" => "op_gt_eq",
"===" => "op_case_eq",
"=~" => "op_apply",
"|" => "op_or",
"&" => "op_and",
"^" => "op_xor",
"[]" => "op_fetch",
"[]=" => "op_store"
}.each do |(what, by)|
result.gsub!(what, by)
end
return result
end
end
if __FILE__ == $0
file = ARGV.shift
load(file)
Extracter.process(file)
end