picotest 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README ADDED
@@ -0,0 +1,177 @@
1
+ = Picotest - simple reduced tests
2
+
3
+ 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
4
+
5
+ == Installation
6
+
7
+ The install is as simple as execute the well-known gem install:
8
+
9
+ sudo gem install picotest
10
+
11
+ == Known Limitations & Issues
12
+
13
+ * Reverse oracle test only can be compared using exact equals (see sqrt example)
14
+ * Metaprogramming of input/output pairs are ugly
15
+ * Oracle testing does not allow expectation test making difficult to debug failing tests
16
+ * Complete lack of RDoc
17
+
18
+ == Usage
19
+
20
+ To use picotest you should write suites using suite method, each suite is defined by a hash and an optional
21
+ description, the keys of the hash are used as input for the method and the corresponding values as expected output
22
+
23
+ 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:
24
+
25
+ suite(1 => 1, 4 => 2, 9 => 3)
26
+
27
+ Or:
28
+
29
+ suite("integer sqrt", 1 => 1, 4 => 2, 9 => 3)
30
+
31
+ After that, you should specify the test:
32
+
33
+ suite("integer sqrt", 1 => 1, 4 => 2, 9 => 3).test(Math.method(:sqrt))
34
+
35
+ To run test, you should set the environment variable PICOTEST_RUN to 1
36
+
37
+ === Environment variables
38
+
39
+ All environments are activated setting their value to 1, other values distinct from 1 will be ignored
40
+
41
+ PICOTEST_RUN enables the execution of test
42
+ PICOTEST_REPORT enables reports of test on standard output and disables Picotest::Fail exceptions when test fails
43
+ PICOTEST_AUTOTEST enables tests of picotest itself
44
+ PICOTEST_FAIL simulate failure of all tests, for picotest test purpose
45
+
46
+ == Code Examples
47
+
48
+ See samples directory under the gem root
49
+
50
+ === Example 1: basic usage of suite and test method
51
+
52
+ class X
53
+ def foo(data)
54
+ data.split(";")[2].to_f
55
+ end
56
+ end
57
+
58
+ suite( "aaa;xxx;1.0;999" => 1.0, ";;3.2" => 3.2).test X.new.method(:foo)
59
+
60
+ === Example 2: Input sets
61
+
62
+ Both -2 and 2 should evaluate to 4 on lambda{|x|x*x}
63
+
64
+ suite(_set(-2,2) => 4).test lambda{|x|x*x}
65
+
66
+ === Example 3: Oracle testing
67
+
68
+ Using a lambda as value on the hash can be used to specify a rule to validate output-input pairs, useful for oracle testing
69
+
70
+ suite(4 => lambda{|y,x| y*y == x}).test Math.method(:sqrt)
71
+
72
+ === Example 4: Oracle testing with input sets
73
+
74
+ This time, the validation rule must considerate the imprecision of floats and check using a error margin
75
+
76
+ suite(_set(1,2,3,4,5) => lambda{|y,x| (y*y - x).abs<0.00001}).test Math.method(:sqrt)
77
+ suite(_set(*(1..100) ) => lambda{|y,x| (y*y - x).abs<0.00001}).test Math.method(:sqrt)
78
+
79
+ === Example 5: Reversed oracle
80
+
81
+ When valid inputs can be generated from output (i.e. float 1.0 output correspond to string "aaa;xxx;1.0;" input)
82
+
83
+ class X
84
+ def foo(data)
85
+ data.split(";")[2].to_f
86
+ end
87
+ end
88
+
89
+ suite( lambda{|x| ";;#{x}"} => _set(*(1..20).map(&:to_f)) ).test X.new.method(:foo)
90
+
91
+ === Example 6: Testing methods of input objects
92
+
93
+ suite( "a b" => ["a","b"]).test :split
94
+ suite( lambda{|x|x.join " "} => _set(["a","b"], ["x","y","z","1"], ["aaa","bbb"]) ).test :split
95
+
96
+ === Example 7: Testing multiple argument inputs
97
+
98
+ class X
99
+ def sum(*x)
100
+ x.inject{|a,b| a+b}
101
+ end
102
+ end
103
+
104
+ suite( 1 => 1, [1,2] => 3, [1,2,3] => 6 ).test X.new.method(:sum)
105
+ suite(_set(*(1..100)) => lambda{|y,x| y == x},
106
+ _set([1,2],[3,4],[4,6],[5,6]) => lambda{|y,x1,x2| y == x1+x2},
107
+ _set([1,2,3],[3,4,5],[4,6,7],[5,6,7]) => lambda{|y,x1,x2,x3| y == x1+x2+x3}
108
+ ).test X.new.method(:sum)
109
+
110
+ === Example 8: Mocking instance variable
111
+
112
+ class X
113
+ def foo(data)
114
+ data.split(";")[@fieldno].to_f
115
+ end
116
+ end
117
+
118
+ suite(hash).test X.new.method(:foo).mock(:@fieldno => 2)
119
+
120
+ === Example 9: Mocking method on receiver object
121
+
122
+ class X
123
+ attr_accessor :fieldno
124
+ def foo(data)
125
+ data.split(";")[fieldno].to_f
126
+ end
127
+ end
128
+
129
+ suite(hash).test X.new.method(:foo).mock(:fieldno => 2)
130
+
131
+ === Example 10: Mock objects
132
+
133
+ class X
134
+ attr_accessor :another_object
135
+ def foo(data)
136
+ data.split(";")[another_object.fieldno].to_f
137
+ end
138
+ end
139
+
140
+ suite(hash).test X.new.method(:foo).mock(:another_object => mock(:fieldno => 2) )
141
+
142
+ === Example 11: Mocking method on receiver object
143
+
144
+ class X
145
+ attr_accessor :another_object
146
+ def foo(data)
147
+ another_object.invented_here_split(data,";")[@fieldno].to_f
148
+ end
149
+ end
150
+
151
+ # you can specify the implementatioin of methods using lambdas
152
+ suite(hash).test X.new.method(:foo).mock(
153
+ :another_object => mock(:invented_here_split => lambda{|str,sep| str.split(sep) }),
154
+ :@fieldno => 2 )
155
+
156
+ # Since :split.to_proc is equivalent to lambda{|str,sep| str.split(sep) }, you can also use:
157
+ suite(hash).test X.new.method(:foo).mock(
158
+ :another_object => mock(:invented_here_split => :split.to_proc),
159
+ :@fieldno => 2 )
160
+
161
+
162
+ === Example 12: Exceptions
163
+
164
+ class X
165
+ def foo(a)
166
+ raise ArgumentError unless a.kind_of? Numeric
167
+
168
+ a*2
169
+ end
170
+ end
171
+
172
+ suite(1 => 2, 2 => 4, 3 => 6, "00" => _raise(ArgumentError)).test(X.new.method(:foo))
173
+
174
+ == Copying
175
+
176
+ Copyright (c) 2012 Dario Seminara, released under the GPL License (see LICENSE)
177
+
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+ require "rspec/core/rake_task"
7
+
8
+ spec = Gem::Specification.new do |s|
9
+ s.name = 'picotest'
10
+ s.version = '0.0.1'
11
+ s.author = 'Dario Seminara'
12
+ s.email = 'robertodarioseminara@gmail.com'
13
+ s.platform = Gem::Platform::RUBY
14
+ s.summary = 'Test pico framework. One-line test suite'
15
+ s.homepage = "http://github.com/tario/picotest"
16
+ s.has_rdoc = true
17
+ s.extra_rdoc_files = [ 'README' ]
18
+ s.files = Dir.glob("{samples,lib}/**/*") +[ 'LICENSE', 'AUTHORS', 'README', 'Rakefile', 'CHANGELOG' ]
19
+ end
20
+
21
+ desc 'Run tests'
22
+ task :test do
23
+ ENV['PICOTEST_AUTOTEST'] = '1'
24
+ ENV['PICOTEST_REPORT'] = '1'
25
+ ENV['PICOTEST_RUN'] = '1'
26
+ require "picotest"
27
+ end
28
+
29
+ desc 'Generate RDoc'
30
+ Rake::RDocTask.new :rdoc do |rd|
31
+ rd.rdoc_dir = 'doc'
32
+ rd.rdoc_files.add 'lib', 'README'
33
+ rd.main = 'README'
34
+ end
35
+
36
+ desc 'Build Gem'
37
+ Rake::GemPackageTask.new spec do |pkg|
38
+ pkg.need_tar = true
39
+ end
40
+
41
+ desc 'Clean up'
42
+ task :clean => [ :clobber_rdoc, :clobber_package ]
43
+
44
+ desc 'Clean up'
45
+ task :clobber => [ :clean ]
46
+
47
+
@@ -0,0 +1,27 @@
1
+ =begin
2
+
3
+ This file is part of the picotest project, http://github.com/tario/picotest
4
+
5
+ Copyright (c) 2012 Roberto Dario Seminara <robertodarioseminara@gmail.com>
6
+
7
+ picotest is free software: you can redistribute it and/or modify
8
+ it under the terms of the gnu general public license as published by
9
+ the free software foundation, either version 3 of the license, or
10
+ (at your option) any later version.
11
+
12
+ picotest is distributed in the hope that it will be useful,
13
+ but without any warranty; without even the implied warranty of
14
+ merchantability or fitness for a particular purpose. see the
15
+ gnu general public license for more details.
16
+
17
+ you should have received a copy of the gnu general public license
18
+ along with picotest. if not, see <http://www.gnu.org/licenses/>.
19
+
20
+ =end
21
+ require "picotest/picomock"
22
+ require "picotest/version"
23
+ require "picotest/core"
24
+ require "picotest/autotest"
25
+ module Picotest
26
+ end
27
+
@@ -0,0 +1,87 @@
1
+ =begin
2
+
3
+ This file is part of the picotest project, http://github.com/tario/picotest
4
+
5
+ Copyright (c) 2012 Roberto Dario Seminara <robertodarioseminara@gmail.com>
6
+
7
+ picotest is free software: you can redistribute it and/or modify
8
+ it under the terms of the gnu general public license as published by
9
+ the free software foundation, either version 3 of the license, or
10
+ (at your option) any later version.
11
+
12
+ picotest is distributed in the hope that it will be useful,
13
+ but without any warranty; without even the implied warranty of
14
+ merchantability or fitness for a particular purpose. see the
15
+ gnu general public license for more details.
16
+
17
+ you should have received a copy of the gnu general public license
18
+ along with picotest. if not, see <http://www.gnu.org/licenses/>.
19
+
20
+ =end
21
+ # pico test testing himself
22
+ require "picotest/autotest/samples"
23
+
24
+ module Picotest
25
+
26
+ if ENV['PICOTEST_AUTOTEST'] == '1'
27
+
28
+
29
+ suite("suiteure should accept :test method", [] => lambda{|x| x.respond_to? :test}).test(method(:suite))
30
+
31
+ suite("raise Fail when tested method raises Fail",
32
+ [1] => _raise(Fail) ).test(lambda{|x|raise Fail})
33
+
34
+ failing_suite = suite("always fails", [] => lambda{|x| false})
35
+ suite("test should raise Fail when condition returns false",
36
+ [lambda{}] => _raise(Fail) ).test(failing_suite.method(:test))
37
+
38
+ suite("message of Fail should be 'Test fail: always fails' when expectation title is 'always fails'",
39
+ [lambda{}] => _raise(Fail, "Test fail: always fails") ).test(failing_suite.method(:test))
40
+
41
+ failing_suite = suite("unexpected message", [] => lambda{|x| false})
42
+ message_suiteure = suite("Should raise Fail with expected message", [lambda{}] => _raise(Fail, "Test fail: expected message") )
43
+
44
+ suite("wrong fail message should raise fail", [failing_suite.method(:test)] => _raise(Fail)).test message_suiteure.method(:test)
45
+
46
+ failing_suite = suite("always always fails", [] => lambda{|x| false})
47
+ suite("message of Fail should be 'Test fail: always always fails' when expectation title is 'always always fails'",
48
+ [lambda{}] => _raise(Fail, "Test fail: always always fails") ).test(failing_suite.method(:test))
49
+
50
+ failing_suite = suite([]=>lambda{|x|false})
51
+ suite("failing suite with lambda{|x|false} and without fail message should fail", [lambda{}]=>_raise(Fail)).
52
+ test(failing_suite.method(:test))
53
+
54
+ passing_suite = suite("should not raise",[]=>lambda{|x|true})
55
+ suite("passing suite with lambda{|x|true} should not fail", [lambda{}]=>_not_raise).
56
+ test(passing_suite.method(:test))
57
+
58
+ passing_suite = suite([]=>lambda{|x|true})
59
+ suite("passing suite with lambda{|x|true} and without fail message should not fail", [lambda{}]=>_not_raise).
60
+ test(passing_suite.method(:test))
61
+
62
+ suite("numeric assert should not fail",
63
+ [lambda{|x|x}] => _not_raise,
64
+ [lambda{|x|1}] => _not_raise,
65
+ [lambda{|x|0}] => _raise(Fail)).test(
66
+ suite([1] => 1).method(:test)
67
+ )
68
+
69
+ suite("_set([1],[2],[3]) should pass with lambda{|x| [1,2,3].include? x}",
70
+ [ lambda{|x| [1,2,3].include?(x) ? 4: nil} ] => _not_raise
71
+ ).test( suite(_set([1],[2],[3]) => 4).method(:test) )
72
+
73
+ suite("_set([1],[2],[3]) should pass with lambda{|x| [1,2,3].include? x}",
74
+ [ lambda{|x| [1,2,3].include?(x) ? 4: nil} ] => _not_raise
75
+ ).test( suite(_set(1,2,3) => 4).method(:test) )
76
+
77
+ class X
78
+ def foo(*x)
79
+ @bar.foo(*x)
80
+ end
81
+ end
82
+
83
+ suite( 1 => 1, 2 => 4, 3 => 9).test( X.new.method(:foo).mock( :@bar => mock(:foo => lambda{|x|x*x} ) ))
84
+
85
+ end
86
+ end
87
+
@@ -0,0 +1,63 @@
1
+ =begin
2
+
3
+ This file is part of the picotest project, http://github.com/tario/picotest
4
+
5
+ Copyright (c) 2012 Roberto Dario Seminara <robertodarioseminara@gmail.com>
6
+
7
+ picotest is free software: you can redistribute it and/or modify
8
+ it under the terms of the gnu general public license as published by
9
+ the free software foundation, either version 3 of the license, or
10
+ (at your option) any later version.
11
+
12
+ picotest is distributed in the hope that it will be useful,
13
+ but without any warranty; without even the implied warranty of
14
+ merchantability or fitness for a particular purpose. see the
15
+ gnu general public license for more details.
16
+
17
+ you should have received a copy of the gnu general public license
18
+ along with picotest. if not, see <http://www.gnu.org/licenses/>.
19
+
20
+ =end
21
+ module Picotest
22
+
23
+ if ENV['PICOTEST_AUTOTEST'] == '1'
24
+ # normal test (as you could do the test)
25
+ suite("squared 1,2,3,4", [1] => 1, [2] => 4, [3] => 9, [4] => 16).test lambda{|x| x*x}
26
+ suite("negative number elevated to 2",_set([1],[-1]) => 1, _set([2],[-2]) => 4, _set([3],[-3]) => 9, _set([4],[-4]) => 16).test lambda{|x| x*x}
27
+
28
+ suite("sqrt of 1,4,9,16",[1] => 1, [4] => 2, [9] => 3, [16] => 4).test Math.method(:sqrt)
29
+
30
+ suite("sqrt of pf 1,4,9,16,25", _set(1,4,9,16,25) => lambda{|y,x| y**2==x}).test Math.method(:sqrt)
31
+ suite("sqrt of 1 to 20 elevated to 2", lambda{|x| x**2} => _set(*(1..20))).test Math.method(:sqrt)
32
+
33
+ suite("sqrt of 1 to 100",_set(*(1..100)) => lambda{|y,x| (y**2 - x.to_f).abs < 0.000000005}).test Math.method(:sqrt)
34
+
35
+ # all last four togheter
36
+
37
+ suite("sqrt", [1] => 1, [4] => 2, [9] => 3, [16] => 4,
38
+ _set(1,4,9,16,25) => lambda{|y,x| y**2==x},
39
+ lambda{|x| x**2} => _set(*(1..20)),
40
+ _set(*(1..100)) => lambda{|y,x| y**2 - x.to_f < 0.000000005} ).test(Math.method(:sqrt))
41
+
42
+ # picotest testing the test
43
+ suite(
44
+ [lambda{|x|x*x}] => _not_raise,
45
+ [lambda{|x|x**2}] => _not_raise,
46
+ [lambda{|x| case x; when 1; 1; when 2; 4; when 3; 9; when 4; 16; end}] => _not_raise,
47
+ [lambda{|x| case x; when 1; 1; when 2; 4; when 3; 9; when 4; 15; end}] => _raise(Fail)
48
+ ).test(
49
+ suite([1] => 1, [2] => 4, [3] => 9, [4] => 16).method(:test)
50
+ )
51
+
52
+ # same as the previous but using another syntax
53
+ suite(
54
+ _set(
55
+ [lambda{|x|x*x}],[lambda{|x|x**2}], [lambda{|x| case x; when 1; 1; when 2; 4; when 3; 9; when 4; 16; end}]) => _not_raise,
56
+ [lambda{|x| case x; when 1; 1; when 2; 4; when 3; 9; when 4; 15; end}] => _raise(Fail)
57
+ ).test(
58
+ suite([1] => 1, [2] => 4, [3] => 9, [4] => 16).method(:test)
59
+ )
60
+
61
+ end
62
+ end
63
+
@@ -0,0 +1,231 @@
1
+ =begin
2
+
3
+ This file is part of the picotest project, http://github.com/tario/picotest
4
+
5
+ Copyright (c) 2012 Roberto Dario Seminara <robertodarioseminara@gmail.com>
6
+
7
+ picotest is free software: you can redistribute it and/or modify
8
+ it under the terms of the gnu general public license as published by
9
+ the free software foundation, either version 3 of the license, or
10
+ (at your option) any later version.
11
+
12
+ picotest is distributed in the hope that it will be useful,
13
+ but without any warranty; without even the implied warranty of
14
+ merchantability or fitness for a particular purpose. see the
15
+ gnu general public license for more details.
16
+
17
+ you should have received a copy of the gnu general public license
18
+ along with picotest. if not, see <http://www.gnu.org/licenses/>.
19
+
20
+ =end
21
+ class Object
22
+ def to_test_proc
23
+ Picotest::ConstantTestProc.new(self)
24
+ end
25
+
26
+ def to_input_set
27
+ Picotest::InputSet.new([self])
28
+ end
29
+ end
30
+
31
+ class Proc
32
+ def to_test_proc
33
+ Picotest::ProcTestProc.new(self)
34
+ end
35
+ end
36
+
37
+ module Picotest
38
+ class ProcTestProc
39
+ attr_accessor :expectation_text
40
+
41
+ def initialize(inner_proc)
42
+ @inner_proc = inner_proc
43
+ @expectation_text = ""
44
+ end
45
+
46
+ def call(m,*i)
47
+ o = m.call(*i)
48
+
49
+ if i.size+1 == @inner_proc.arity
50
+ @inner_proc.call(o,*i)
51
+ elsif @inner_proc.arity == 1
52
+ @inner_proc.call(o)
53
+ else
54
+ raise RuntimeError, "wrong arity for lambda"
55
+ end
56
+ end
57
+ end
58
+
59
+ class ConstantTestProc
60
+ def initialize(const)
61
+ @const = const
62
+ end
63
+
64
+ def call(m,*i)
65
+ @const == m.call(*i)
66
+ end
67
+
68
+ def expectation_text
69
+ @const.inspect
70
+ end
71
+ end
72
+
73
+ class Fail < Exception
74
+ end
75
+
76
+ class InputSet
77
+ def initialize(array); @array = array; end
78
+
79
+ def to_input_set
80
+ self
81
+ end
82
+
83
+ def each(&blk)
84
+ @array.each(&blk)
85
+ end
86
+ end
87
+
88
+ class RaiseAssert
89
+ attr_reader :expectation_text
90
+ def initialize(ext,&blk)
91
+ @blk = blk
92
+ @expectation_text = ext
93
+ end
94
+
95
+ def to_test_proc
96
+ self
97
+ end
98
+
99
+ def call(*x)
100
+ @blk.call(*x)
101
+ end
102
+ end
103
+
104
+ class Suite
105
+ def initialize(fail_message,fxtdata,position)
106
+ @fxtdata=fxtdata
107
+ @fail_message=fail_message
108
+ @position = position
109
+
110
+ @report = ENV["PICOTEST_REPORT"] == "1" ? true : false
111
+ @raise_fail = @report ? false : true
112
+ @run = ENV["PICOTEST_RUN"] == "1" ? true : false
113
+ @emulate_fail = ENV["PICOTEST_FAIL"] == "1" ? true : false
114
+ end
115
+
116
+ def test(m)
117
+ return unless @run
118
+
119
+ if m.instance_of? Method
120
+ if m.owner == Picotest::Suite
121
+ m.receiver.instance_eval{@raise_fail = true}
122
+ m.receiver.instance_eval{@emulate_fail = false}
123
+ end
124
+ end
125
+ m = m.to_proc
126
+
127
+ test_fail = false
128
+
129
+ @fxtdata.each do |k,o|
130
+ if k.respond_to? :to_proc
131
+ oracle = k.to_proc
132
+ o.to_input_set.each do|_exp_o|
133
+ i = oracle.call(_exp_o)
134
+ _o = m.call(*i)
135
+ if not _o == _exp_o or @emulate_fail
136
+ test_fail = true
137
+
138
+ if @raise_fail
139
+ raise Picotest::Fail,'Test fail: '+@fail_message
140
+ else
141
+
142
+ location = if m.respond_to?(:source_location)
143
+ m.source_location
144
+ end
145
+
146
+ print "fail #{@fail_message}. Expected output:#{_exp_o} , received:#{_o}
147
+ suite at #{@position}
148
+ test at #{caller[0]}
149
+ #{location ? "method location at "+location.join(":") : "" }
150
+ "
151
+ end
152
+ end
153
+ end
154
+ else
155
+ k.to_input_set.each do|i|
156
+ test_proc = o.to_test_proc
157
+ if not test_proc.call(m,*i) or @emulate_fail
158
+ test_fail = true
159
+
160
+ if @raise_fail
161
+ raise Picotest::Fail,'Test fail: '+@fail_message
162
+ else
163
+
164
+ location = if m.respond_to?(:source_location)
165
+ m.source_location
166
+ end
167
+
168
+ print "fail #{@fail_message}. Input: #{i.inspect}, Expected: #{test_proc.expectation_text}
169
+ suite at #{@position}
170
+ test at #{caller[0]}
171
+ #{location ? "method location at "+location.join(":") : "" }
172
+
173
+ "
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ unless test_fail
181
+ print "Test sucessfull at #{caller[0]}\n" if @report
182
+ end
183
+
184
+ end
185
+ end
186
+
187
+ module GlobalMethods
188
+ def suite(*args)
189
+ if args.size==1
190
+ Suite.new("",args.first,caller[0])
191
+ else
192
+ Suite.new(args.first,args.last,caller[0])
193
+ end
194
+ end
195
+
196
+ def _set(*args)
197
+ InputSet.new(args)
198
+ end
199
+
200
+ def _not_raise
201
+ RaiseAssert.new("not raise exception") {|m,*i|
202
+ begin
203
+ m.call(*i)
204
+ true
205
+ rescue
206
+ false
207
+ end
208
+ }
209
+ end
210
+
211
+ def _raise(expected, expected_message = nil)
212
+ RaiseAssert.new("raise #{expected}") {|m,*i|
213
+ begin
214
+ m.call(*i)
215
+ false
216
+ rescue expected => e
217
+ if expected_message
218
+ e.message == expected_message
219
+ else
220
+ true
221
+ end
222
+ end
223
+ }
224
+ end
225
+ end
226
+ end
227
+
228
+ class Object
229
+ include Picotest::GlobalMethods
230
+ end
231
+