/picotest

Test pico framework. Write entire test suites in a few lines

Primary LanguageRubyGNU General Public License v3.0GPL-3.0

= Picotest - simple reduced tests

Picotest is a gem which allows to write complete test suites in a few lines, targeted primarily to test separated methods, algorithms or little snippets of code

== Installation

The install is as simple as execute the well-known gem install:

sudo gem install picotest

== Known Limitations & Issues

* Reverse oracle test only can be compared using exact equals (see sqrt example)
* Metaprogramming of input/output pairs are ugly
* Oracle testing does not allow expectation test making difficult to debug failing tests
* Complete lack of RDoc

== Usage

To use picotest you should write suites using suite method, each suite is defined by a hash and an optional
description, the keys of the hash are used as input for the method and the corresponding values as expected output

For example, to specify that the method receiving 1 should return 1, receiving 4 should return 2 and receiving 9 should return 3, use the following syntax:

  suite(1 => 1, 4 => 2, 9 => 3)

Or:

  suite("integer sqrt", 1 => 1, 4 => 2, 9 => 3)

After that, you should specify the test:

  suite("integer sqrt", 1 => 1, 4 => 2, 9 => 3).test(Math.method(:sqrt))

To run test, you should set the environment variable PICOTEST_RUN to 1

=== Environment variables

All environments are activated setting their value to 1, other values distinct from 1 will be ignored

PICOTEST_RUN        enables the execution of test
PICOTEST_REPORT     enables reports of test on standard output and disables Picotest::Fail exceptions when test fails
PICOTEST_AUTOTEST   enables tests of picotest itself
PICOTEST_FAIL       simulate failure of all tests, for picotest test purpose

== Code Examples

See samples directory under the gem root

=== Example 1: basic usage of suite and test method

  class X
    def foo(data)
      data.split(";")[2].to_f
    end
  end

  suite( "aaa;xxx;1.0;999" => 1.0, ";;3.2" => 3.2).test X.new.method(:foo)

=== Example 2: Input sets

Both -2 and 2 should evaluate to 4 on lambda{|x|x*x}

  suite(_set(-2,2) => 4).test lambda{|x|x*x}

=== Example 3: Oracle testing

Using a lambda as value on the hash can be used to specify a rule to validate output-input pairs, useful for oracle testing

  suite(4 => lambda{|y,x| y*y == x}).test Math.method(:sqrt)

=== Example 4: Oracle testing with input sets

This time, the validation rule must considerate the imprecision of floats and check using a error margin

  suite(_set(1,2,3,4,5) => lambda{|y,x| (y*y - x).abs<0.00001}).test Math.method(:sqrt)
  suite(_set(*(1..100) ) => lambda{|y,x| (y*y - x).abs<0.00001}).test Math.method(:sqrt)

=== Example 5: Reversed oracle

When valid inputs can be generated from output (i.e. float 1.0 output correspond to string "aaa;xxx;1.0;" input)

  class X
    def foo(data)
      data.split(";")[2].to_f
    end
  end

  suite( lambda{|x| ";;#{x}"} => _set(*(1..20).map(&:to_f)) ).test X.new.method(:foo)

=== Example 6: Testing methods of input objects

  suite( "a b" => ["a","b"]).test :split
  suite( lambda{|x|x.join " "} => _set(["a","b"], ["x","y","z","1"], ["aaa","bbb"]) ).test :split

=== Example 7: Testing multiple argument inputs

  class X
    def sum(*x)
      x.inject{|a,b| a+b}
    end
  end

  suite( 1 => 1, [1,2] => 3, [1,2,3] => 6 ).test X.new.method(:sum)
  suite(_set(*(1..100)) => lambda{|y,x| y == x},
      _set([1,2],[3,4],[4,6],[5,6]) => lambda{|y,x1,x2| y == x1+x2},
      _set([1,2,3],[3,4,5],[4,6,7],[5,6,7]) => lambda{|y,x1,x2,x3| y == x1+x2+x3}
      ).test X.new.method(:sum)

=== Example 8: Mocking instance variable

  class X
    def foo(data)
      data.split(";")[@fieldno].to_f
    end
  end

  suite(hash).test X.new.method(:foo).mock(:@fieldno => 2)

=== Example 9: Mocking method on receiver object

  class X
    attr_accessor :fieldno
    def foo(data)
      data.split(";")[fieldno].to_f
    end
  end

  suite(hash).test X.new.method(:foo).mock(:fieldno => 2)

=== Example 10: Mock objects

  class X
    attr_accessor :another_object
    def foo(data)
      data.split(";")[another_object.fieldno].to_f
    end
  end

  suite(hash).test X.new.method(:foo).mock(:another_object => mock(:fieldno => 2) )

=== Example 11: Mocking method on receiver object

  class X
    attr_accessor :another_object
    def foo(data)
      another_object.invented_here_split(data,";")[@fieldno].to_f
    end
  end

  # you can specify the implementatioin of methods using lambdas
  suite(hash).test X.new.method(:foo).mock(
    :another_object => mock(:invented_here_split => lambda{|str,sep| str.split(sep) }), 
    :@fieldno => 2 )

  # Since :split.to_proc is equivalent to lambda{|str,sep| str.split(sep) }, you can also use:
  suite(hash).test X.new.method(:foo).mock(
    :another_object => mock(:invented_here_split => :split.to_proc), 
    :@fieldno => 2 )


=== Example 12: Exceptions

  class X
    def foo(a)
      raise ArgumentError unless a.kind_of? Numeric

      a*2
    end
  end

  suite(1 => 2, 2 => 4, 3 => 6, "00" => _raise(ArgumentError)).test(X.new.method(:foo))

== Copying

Copyright (c) 2012 Dario Seminara, released under the GPL License (see LICENSE)