picotest 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+