Literate testing?

I was documenting a class and found myself often copying unit tests
and pasting them as examples. I think I also read something about
tests embedded in doc strings in Python. So in the interest of DRY I
figured something like this could be desirable:

  class Calculator
    extend Literate

    example
      # Addition
      add(2, 8) == 10
    end
  
    def add(a, b)
      return a + b
    end
  end
  
I then came up with a stupid but working proof of concept:
  
  class Calculator
    extend Literate
    
    example(__FILE__, __LINE__) do |calc|
      # Addition
      calc.add(2, 8) == 10
    end
  
    def add(a, b)
      return a + b
    end
  
    example(__FILE__, __LINE__) do |calc|
      # Integer division
      calc.div(10, 2) == 5
    end
  
    def div(a, b)
      return a / b + 1
    end
  end

When examples are run as tests and a test does not pass (as the div
example above), this is the output:

  Test passed at line 17.
  Test failed at line 27:
      # Integer division
      calc.div(10, 2) == 5

This is the rest:
  
  module Literate
    def examples
      @examples ||= []
    end
  
    def example(file, line, &example)
      @examples ||= []
      @examples << [file, line, example]
    end
  end
  
  Calculator.examples.each do |file, line, example|
    c = Calculator.new
    if example.call(c)
      puts "Test passed at line #{line}."
    else
      puts "Test failed at line #{line}:"
      puts File.readlines(file)[line, 2]
    end
  end
  
What do you think?

Massimiliano

Massimiliano Mirra - bard wrote:

  class Calculator
    extend Literate
        example(__FILE__, __LINE__) do |calc|

...

Since you are passing a block, you can get the file and line from it, as in:

def get_fl(&bl)
   eval "[__FILE__, __LINE__]", bl
end

class Foo
   puts get_fl {}
   puts [__FILE__, __LINE__]
end

Massimiliano Mirra - bard wrote:

I was documenting a class and found myself often copying unit tests
and pasting them as examples. I think I also read something about
tests embedded in doc strings in Python. So in the interest of DRY I
figured something like this could be desirable:

  class Calculator
    extend Literate

    example
      # Addition
      add(2, 8) == 10
    end
      def add(a, b)
      return a + b
    end
  end
  I then came up with a stupid but working proof of concept:
    class Calculator
    extend Literate
        example(__FILE__, __LINE__) do |calc|
      # Addition
      calc.add(2, 8) == 10
    end

Are you planning to extract the example from the source code and generate documentation files? This could be done using the file and line information and some parsing or just good guesses about indentation.

I believe you can make it look a bit better by using Kernel#caller
and Module#method_added; it would also be nice to generate test
cases for Test::Unit (for instance by defining the test_* methods in
MyClass::LiterateTests so that they can be included in a test suite
later).

Reminds me of Florian Groß' 'test extractor' [109712].
It works "the other way around" though, by extracting the tests from the
comments (similar to what you describe for Python).

···

On Sat, Aug 28, 2004 at 05:40:33AM +0900, Massimiliano Mirra - bard wrote:

I was documenting a class and found myself often copying unit tests
and pasting them as examples. I think I also read something about
tests embedded in doc strings in Python. So in the interest of DRY I
figured something like this could be desirable:

  class Calculator
    extend Literate

    example
      # Addition
      add(2, 8) == 10
    end
  
    def add(a, b)
      return a + b
    end
  end
  
I then came up with a stupid but working proof of concept:
  
  class Calculator
    extend Literate
    
    example(__FILE__, __LINE__) do |calc|
      # Addition
      calc.add(2, 8) == 10
    end

--
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

Mauricio Fernández <batsman.geo@yahoo.com> writes:

I believe you can make it look a bit better by using Kernel#caller
and Module#method_added;

I confess I have absolutely no idea how. :slight_smile:

it would also be nice to generate test
cases for Test::Unit (for instance by defining the test_* methods in
MyClass::LiterateTests so that they can be included in a test suite
later).

Nice. I tried, and the last call shows test_* methods get defined,
but Test::Unit says `No tests were specified.'.

  require "test/unit"
  
  module Literate
    def method_missing(id, *args, &block)
      if id.to_s =~ /example_/
        name = Regexp.last_match.post_match
        self::LiterateTests.send(:define_method, "test_#{name}".intern, &block)
      else
        super(id, *args, &block)
      end
    end
  end
  
  class Calculator
    extend Literate
  
    module LiterateTests
    end
  
    example_add do
      assert @calc.add(2, 8) == 10
    end
  
    def add(a, b)
      return a + b
    end
  
    example_div do
      assert @calc.div(10, 2) == 5
    end
  
    def div(a, b)
      return a / b + 1
    end
  end
  
  class TestCalculator < Test::Unit::TestCase
    include Calculator::LiterateTests
  
    def setup
      @calc = Calculator.new
    end
  end
  
  p TestCalculator.public_instance_methods

Joel VanderWerf <vjoel@PATH.Berkeley.EDU> writes:

Are you planning to extract the example from the source code and
generate documentation files?

I'd rather say `hoping', but yes, that would be the purpose.

This could be done using the file and line
information and some parsing or just good guesses about indentation.

Yep. Or by hacking rdoc a bit.

Thanks for the bit on eval.

Massimiliano

I think you were bitten by the IMHO bug explained in
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/2829

···

On Sat, Aug 28, 2004 at 07:45:33AM +0900, Massimiliano Mirra - bard wrote:

Mauricio Fernández <batsman.geo@yahoo.com> writes:

> I believe you can make it look a bit better by using Kernel#caller
> and Module#method_added;

I confess I have absolutely no idea how. :slight_smile:

> it would also be nice to generate test
> cases for Test::Unit (for instance by defining the test_* methods in
> MyClass::LiterateTests so that they can be included in a test suite
> later).

Nice. I tried, and the last call shows test_* methods get defined,
but Test::Unit says `No tests were specified.'.

--
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com