minitest 1.7.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,30 @@
1
+ === 2.0.0 / 2010-11-11
2
+
3
+ * 3 major enhancements:
4
+
5
+ * Added minitest/benchmark! Assert your performance! YAY!
6
+ * Refactored runner to allow for more extensibility. See minitest/benchmark.
7
+ * This makes the runner backwards incompatible in some ways!
8
+
9
+ * 9 minor enhancements:
10
+
11
+ * Added MiniTest::Unit.after_tests { ... }
12
+ * Improved output by adding test rates and a more sortable verbose format
13
+ * Improved readme based on feedback from others
14
+ * Added io method to TestCase. If used, it'll supplant '.EF' output.
15
+ * Refactored IO in MiniTest::Unit.
16
+ * Refactored _run_anything to _run_suite to make it easier to wrap (ngauthier)
17
+ * Spec class names are now the unmunged descriptions (btakita)
18
+ * YAY for not having redundant rdoc/readmes!
19
+ * Help output is now generated from the flags you passed straight up.
20
+
21
+ * 4 bug fixes:
22
+
23
+ * Fixed scoping issue on minitest/mock (srbaker/prosperity)
24
+ * Fixed some of the assertion default messages
25
+ * Fixes autorun when on windows with ruby install on different drive (larsch)
26
+ * Fixed rdoc output bug in spec.rb
27
+
1
28
  === 1.7.2 / 2010-09-23
2
29
 
3
30
  * 3 bug fixes:
@@ -5,9 +5,12 @@ README.txt
5
5
  Rakefile
6
6
  design_rationale.rb
7
7
  lib/minitest/autorun.rb
8
+ lib/minitest/benchmark.rb
8
9
  lib/minitest/mock.rb
10
+ lib/minitest/pride.rb
9
11
  lib/minitest/spec.rb
10
12
  lib/minitest/unit.rb
11
- test/test_mini_mock.rb
12
- test/test_mini_spec.rb
13
- test/test_mini_test.rb
13
+ test/test_minitest_benchmark.rb
14
+ test/test_minitest_mock.rb
15
+ test/test_minitest_spec.rb
16
+ test/test_minitest_unit.rb
data/README.txt CHANGED
@@ -1,25 +1,44 @@
1
- = minitest/{unit,spec,mock}
1
+ = minitest/*
2
2
 
3
3
  * http://rubyforge.org/projects/bfts
4
4
 
5
5
  == DESCRIPTION:
6
6
 
7
- minitest/unit is a small and fast replacement for ruby's huge and slow
8
- test/unit. This is meant to be clean and easy to use both as a regular
9
- test writer and for language implementors that need a minimal set of
10
- methods to bootstrap a working unit test suite.
7
+ minitest provides a complete suite of testing facilities supporting
8
+ TDD, BDD, mocking, and benchmarking.
11
9
 
12
- mini/spec is a functionally complete spec engine.
10
+ minitest/unit is a small and incredibly fast unit testing framework.
11
+ It provides a rich set of assertions to make your tests clean and
12
+ readable.
13
13
 
14
- mini/mock, by Steven Baker, is a beautifully tiny mock object framework.
14
+ minitest/spec is a functionally complete spec engine. It hooks onto
15
+ minitest/unit and seamlessly bridges test assertions over to spec
16
+ expectations.
15
17
 
16
- (This package was called miniunit once upon a time)
18
+ minitest/benchmark is an awesome way to assert the performance of your
19
+ algorithms in a repeatable manner. Now you can assert that your newb
20
+ co-worker doesn't replace your linear algorithm with an exponential
21
+ one!
22
+
23
+ minitest/mock by Steven Baker, is a beautifully tiny mock object
24
+ framework.
25
+
26
+ minitest/pride shows pride in testing and adds coloring to your test
27
+ output.
28
+
29
+ minitest/unit is meant to have a clean implementation for language
30
+ implementors that need a minimal set of methods to bootstrap a working
31
+ test suite. For example, there is no magic involved for test-case
32
+ discovery.
17
33
 
18
34
  == FEATURES/PROBLEMS:
19
35
 
20
- * Contains minitest/unit - a simple and clean test system (301 lines!).
21
- * Contains minitest/spec - a simple and clean spec system (52 lines!).
22
- * Contains minitest/mock - a simple and clean mock system (35 lines!).
36
+ * minitest/autorun - the easy and explicit way to run all your tests.
37
+ * minitest/unit - a very fast, simple, and clean test system.
38
+ * minitest/spec - a very fast, simple, and clean spec system.
39
+ * minitest/mock - a simple and clean mock system.
40
+ * minitest/benchmark - an awesome way to assert your algorithm's performance.
41
+ * minitest/pride - show your pride in testing!
23
42
  * Incredibly small and fast runner, but no bells and whistles.
24
43
 
25
44
  == RATIONALE:
@@ -30,100 +49,147 @@ See design_rationale.rb to see how specs and tests work in minitest.
30
49
 
31
50
  Given that you'd like to test the following class:
32
51
 
33
- class Meme
34
- def i_can_has_cheezburger?
35
- "OHAI!"
36
- end
37
-
38
- def does_it_blend?
39
- "YES!"
40
- end
52
+ class Meme
53
+ def i_can_has_cheezburger?
54
+ "OHAI!"
41
55
  end
42
56
 
57
+ def does_it_blend?
58
+ "YES!"
59
+ end
60
+ end
43
61
 
44
62
  === Unit tests
45
63
 
46
- require 'minitest/unit'
47
- MiniTest::Unit.autorun
64
+ require 'minitest/autorun'
48
65
 
49
- class TestMeme < MiniTest::Unit::TestCase
50
- def setup
51
- @meme = Meme.new
52
- end
66
+ class TestMeme < MiniTest::Unit::TestCase
67
+ def setup
68
+ @meme = Meme.new
69
+ end
53
70
 
54
- def test_that_kitty_can_eat
55
- assert_equal "OHAI!", @meme.i_can_has_cheezburger?
56
- end
71
+ def test_that_kitty_can_eat
72
+ assert_equal "OHAI!", @meme.i_can_has_cheezburger?
73
+ end
57
74
 
58
- def test_that_it_doesnt_not_blend
59
- refute_match /^no/i, @meme.does_it_blend?
60
- end
75
+ def test_that_it_doesnt_not_blend
76
+ refute_match /^no/i, @meme.does_it_blend?
61
77
  end
78
+ end
62
79
 
63
80
  === Specs
64
81
 
65
- require 'minitest/spec'
66
- MiniTest::Unit.autorun
82
+ require 'minitest/autorun'
83
+
84
+ describe Meme do
85
+ before do
86
+ @meme = Meme.new
87
+ end
88
+
89
+ describe "when asked about cheeseburgers" do
90
+ it "must respond positively" do
91
+ @meme.i_can_has_cheezburger?.must_equal "OHAI!"
92
+ end
93
+ end
67
94
 
68
- describe Meme do
69
- before do
70
- @meme = Meme.new
95
+ describe "when asked about blending possibilities" do
96
+ it "won't say no" do
97
+ @meme.does_it_blend?.wont_match /^no/i
71
98
  end
99
+ end
100
+ end
101
+
102
+ === Benchmarks
103
+
104
+ Add benchmarks to your regular unit tests. If the unit tests fail, the
105
+ benchmarks won't run.
72
106
 
73
- describe "when asked about cheeseburgers" do
74
- it "should respond positively" do
75
- @meme.i_can_has_cheezburger?.must_equal "OHAI!"
107
+ # optionally run benchmarks, good for CI-only work!
108
+ require 'minitest/benchmark' if ENV["BENCH"]
109
+
110
+ class TestMeme < MiniTest::Unit::TestCase
111
+ # Override self.bench_range or default range is [1, 10, 100, 1_000, 10_000]
112
+ def bench_my_algorithm
113
+ assert_performance_linear 0.9999 do |n| # n is a range value
114
+ n.times do
115
+ @obj.my_algorithm
76
116
  end
77
117
  end
118
+ end
119
+ end
78
120
 
79
- describe "when asked about blending possibilities" do
80
- it "won't say no" do
81
- @meme.does_it_blend?.wont_match /^no/i
121
+ Or add them to your specs. If you make benchmarks optional, you'll
122
+ need to wrap your benchmarks in a conditional since the methods won't
123
+ be defined.
124
+
125
+ describe Meme do
126
+ if ENV["BENCH"] then
127
+ bench_performance_linear "my_algorithm", 0.9999 do |n|
128
+ 100.times do
129
+ @obj.my_algorithm(n)
82
130
  end
83
131
  end
84
132
  end
133
+ end
134
+
135
+ outputs something like:
136
+
137
+ # Running benchmarks:
138
+
139
+ TestBlah 100 1000 10000
140
+ bench_my_algorithm 0.006167 0.079279 0.786993
141
+ bench_other_algorithm 0.061679 0.792797 7.869932
142
+
143
+ Output is tab-delimited to make it easy to paste into a spreadsheet.
85
144
 
86
145
  === Mocks
87
146
 
88
- class MemeAsker
89
- def initialize(meme)
90
- @meme = meme
91
- end
147
+ class MemeAsker
148
+ def initialize(meme)
149
+ @meme = meme
150
+ end
92
151
 
93
- def ask(question)
94
- method = question.tr(" ","_") + "?"
95
- @meme.send(method)
96
- end
152
+ def ask(question)
153
+ method = question.tr(" ","_") + "?"
154
+ @meme.send(method)
97
155
  end
156
+ end
98
157
 
99
- require 'minitest/spec'
100
- require 'minitest/mock'
101
- MiniTest::Unit.autorun
158
+ require 'minitest/autorun'
102
159
 
103
- describe MemeAsker do
104
- before do
105
- @meme = MiniTest::Mock.new
106
- @meme_asker = MemeAsker.new @meme
107
- end
160
+ describe MemeAsker do
161
+ before do
162
+ @meme = MiniTest::Mock.new
163
+ @meme_asker = MemeAsker.new @meme
164
+ end
108
165
 
109
- describe "#ask" do
110
- describe "when passed an unpunctuated question" do
111
- it "should invoke the appropriate predicate method on the meme" do
112
- @meme.expect :does_it_blend?, :return_value
113
- @meme_asker.ask "does it blend"
114
- @meme.verify
115
- end
166
+ describe "#ask" do
167
+ describe "when passed an unpunctuated question" do
168
+ it "should invoke the appropriate predicate method on the meme" do
169
+ @meme.expect :does_it_blend?, :return_value
170
+ @meme_asker.ask "does it blend"
171
+ @meme.verify
116
172
  end
117
173
  end
118
174
  end
175
+ end
119
176
 
120
177
  == REQUIREMENTS:
121
178
 
122
- + Ruby 1.8, maybe even 1.6 or lower. No magic is involved.
179
+ * Ruby 1.8, maybe even 1.6 or lower. No magic is involved.
123
180
 
124
181
  == INSTALL:
125
182
 
126
- + sudo gem install minitest
183
+ sudo gem install minitest
184
+
185
+ On 1.9, you already have it. To get newer candy you can still install
186
+ the gem, but you'll need to activate the gem explicitly to use it:
187
+
188
+ require 'rubygems'
189
+ gem 'minitest' # ensures you're using the gem, and not the built in MT
190
+ require 'minitest/autorun'
191
+
192
+ # ... usual testing stuffs ...
127
193
 
128
194
  == LICENSE:
129
195
 
@@ -0,0 +1,324 @@
1
+ require 'minitest/unit'
2
+ require 'minitest/spec'
3
+
4
+ class MiniTest::Unit
5
+ attr_accessor :runner
6
+
7
+ def run_benchmarks
8
+ _run_anything :benchmark
9
+ end
10
+
11
+ def benchmark_suite_header suite
12
+ "\n#{suite}\t#{suite.bench_range.join("\t")}"
13
+ end
14
+
15
+ class TestCase
16
+ ##
17
+ # Returns a set of ranges stepped exponentially from +min+ to
18
+ # +max+ by powers of +base+. Eg:
19
+ #
20
+ # bench_exp(2, 16, 2) # => [2, 4, 8, 16]
21
+
22
+ def self.bench_exp min, max, base = 10
23
+ min = (Math.log10(min) / Math.log10(base)).to_i
24
+ max = (Math.log10(max) / Math.log10(base)).to_i
25
+
26
+ (min..max).map { |m| base ** m }.to_a
27
+ end
28
+
29
+ ##
30
+ # Returns a set of ranges stepped linearly from +min+ to +max+ by
31
+ # +step+. Eg:
32
+ #
33
+ # bench_linear(20, 40, 10) # => [20, 30, 40]
34
+
35
+ def self.bench_linear min, max, step = 10
36
+ (min..max).step(step).to_a
37
+ rescue LocalJumpError # 1.8.6
38
+ r = []; (min..max).step(step) { |n| r << n }; r
39
+ end
40
+
41
+ ##
42
+ # Returns the benchmark methods (methods that start with bench_)
43
+ # for that class.
44
+
45
+ def self.benchmark_methods # :nodoc:
46
+ public_instance_methods(true).grep(/^bench_/).map { |m| m.to_s }.sort
47
+ end
48
+
49
+ ##
50
+ # Returns all test suites that have benchmark methods.
51
+
52
+ def self.benchmark_suites
53
+ TestCase.test_suites.reject { |s| s.benchmark_methods.empty? }
54
+ end
55
+
56
+ ##
57
+ # Specifies the ranges used for benchmarking for that class.
58
+ # Defaults to exponential growth from 1 to 10k by powers of 10.
59
+ # Override if you need different ranges for your benchmarks.
60
+ #
61
+ # See also: ::bench_exp and ::bench_linear.
62
+
63
+ def self.bench_range
64
+ bench_exp 1, 10_000
65
+ end
66
+
67
+ ##
68
+ # Runs the given +work+, gathering the times of each run. Range
69
+ # and times are then passed to a given +validation+ proc. Outputs
70
+ # the benchmark name and times in tab-separated format, making it
71
+ # easy to paste into a spreadsheet for graphing or further
72
+ # analysis.
73
+ #
74
+ # Ranges are specified by ::bench_range.
75
+ #
76
+ # Eg:
77
+ #
78
+ # def bench_algorithm
79
+ # validation = proc { |x, y| ... }
80
+ # assert_performance validation do |x|
81
+ # @obj.algorithm
82
+ # end
83
+ # end
84
+
85
+ def assert_performance validation, &work
86
+ range = self.class.bench_range
87
+
88
+ io.print "#{__name__}"
89
+
90
+ times = []
91
+
92
+ range.each do |x|
93
+ GC.start
94
+ t0 = Time.now
95
+ instance_exec(x, &work)
96
+ t = Time.now - t0
97
+
98
+ io.print "\t%9.6f" % t
99
+ times << t
100
+ end
101
+ io.puts
102
+
103
+ validation[range, times]
104
+ end
105
+
106
+ ##
107
+ # Runs the given +work+ and asserts that the times gathered fit to
108
+ # match a constant rate (eg, linear slope == 0) within a given error
109
+ # +threshold+.
110
+ #
111
+ # Fit is calculated by #fit_constant.
112
+ #
113
+ # Ranges are specified by ::bench_range.
114
+ #
115
+ # Eg:
116
+ #
117
+ # def bench_algorithm
118
+ # assert_performance_constant 0.9999 do |x|
119
+ # @obj.algorithm
120
+ # end
121
+ # end
122
+
123
+ def assert_performance_constant threshold = 0.99, &work
124
+ validation = proc do |range, times|
125
+ a, b, rr = fit_linear range, times
126
+ assert_in_delta 0, b, 1 - threshold
127
+ [a, b, rr]
128
+ end
129
+
130
+ assert_performance validation, &work
131
+ end
132
+
133
+ ##
134
+ # Runs the given +work+ and asserts that the times gathered fit to
135
+ # match a exponential curve within a given error +threshold+.
136
+ #
137
+ # Fit is calculated by #fit_exponential.
138
+ #
139
+ # Ranges are specified by ::bench_range.
140
+ #
141
+ # Eg:
142
+ #
143
+ # def bench_algorithm
144
+ # assert_performance_exponential 0.9999 do |x|
145
+ # @obj.algorithm
146
+ # end
147
+ # end
148
+
149
+ def assert_performance_exponential threshold = 0.99, &work
150
+ assert_performance validation_for_fit(:exponential, threshold), &work
151
+ end
152
+
153
+ ##
154
+ # Runs the given +work+ and asserts that the times gathered fit to
155
+ # match a straight line within a given error +threshold+.
156
+ #
157
+ # Fit is calculated by #fit_linear.
158
+ #
159
+ # Ranges are specified by ::bench_range.
160
+ #
161
+ # Eg:
162
+ #
163
+ # def bench_algorithm
164
+ # assert_performance_linear 0.9999 do |x|
165
+ # @obj.algorithm
166
+ # end
167
+ # end
168
+
169
+ def assert_performance_linear threshold = 0.99, &work
170
+ assert_performance validation_for_fit(:linear, threshold), &work
171
+ end
172
+
173
+ ##
174
+ # Runs the given +work+ and asserts that the times gathered curve
175
+ # fit to match a power curve within a given error +threshold+.
176
+ #
177
+ # Fit is calculated by #fit_power.
178
+ #
179
+ # Ranges are specified by ::bench_range.
180
+ #
181
+ # Eg:
182
+ #
183
+ # def bench_algorithm
184
+ # assert_performance_power 0.9999 do |x|
185
+ # @obj.algorithm
186
+ # end
187
+ # end
188
+
189
+ def assert_performance_power threshold = 0.99, &work
190
+ assert_performance validation_for_fit(:power, threshold), &work
191
+ end
192
+
193
+ ##
194
+ # Takes an array of x/y pairs and calculates the general R^2 value.
195
+ #
196
+ # See: http://en.wikipedia.org/wiki/Coefficient_of_determination
197
+
198
+ def fit_error xys
199
+ y_bar = sigma(xys) { |x, y| y } / xys.size.to_f
200
+ ss_tot = sigma(xys) { |x, y| (y - y_bar) ** 2 }
201
+ ss_err = sigma(xys) { |x, y| (yield(x) - y) ** 2 }
202
+
203
+ 1 - (ss_err / ss_tot)
204
+ end
205
+
206
+ ##
207
+ # To fit a functional form: y = ae^(bx).
208
+ #
209
+ # Takes x and y values and returns [a, b, r^2].
210
+ #
211
+ # See: http://mathworld.wolfram.com/LeastSquaresFittingExponential.html
212
+
213
+ def fit_exponential xs, ys
214
+ n = xs.size
215
+ xys = xs.zip(ys)
216
+ sxlny = sigma(xys) { |x,y| x * Math.log(y) }
217
+ slny = sigma(xys) { |x,y| Math.log(y) }
218
+ sx2 = sigma(xys) { |x,y| x * x }
219
+ sx = sigma xs
220
+
221
+ c = n * sx2 - sx ** 2
222
+ a = (slny * sx2 - sx * sxlny) / c
223
+ b = ( n * sxlny - sx * slny ) / c
224
+
225
+ return Math.exp(a), b, fit_error(xys) { |x| Math.exp(a + b * x) }
226
+ end
227
+
228
+ ##
229
+ # Fits the functional form: a + bx.
230
+ #
231
+ # Takes x and y values and returns [a, b, r^2].
232
+ #
233
+ # See: http://mathworld.wolfram.com/LeastSquaresFitting.html
234
+
235
+ def fit_linear xs, ys
236
+ n = xs.size
237
+ xys = xs.zip(ys)
238
+ sx = sigma xs
239
+ sy = sigma ys
240
+ sx2 = sigma(xs) { |x| x ** 2 }
241
+ sxy = sigma(xys) { |x,y| x * y }
242
+
243
+ c = n * sx2 - sx**2
244
+ a = (sy * sx2 - sx * sxy) / c
245
+ b = ( n * sxy - sx * sy ) / c
246
+
247
+ return a, b, fit_error(xys) { |x| a + b * x }
248
+ end
249
+
250
+ ##
251
+ # To fit a functional form: y = ax^b.
252
+ #
253
+ # Takes x and y values and returns [a, b, r^2].
254
+ #
255
+ # See: http://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html
256
+
257
+ def fit_power xs, ys
258
+ n = xs.size
259
+ xys = xs.zip(ys)
260
+ slnxlny = sigma(xys) { |x, y| Math.log(x) * Math.log(y) }
261
+ slnx = sigma(xs) { |x | Math.log(x) }
262
+ slny = sigma(ys) { | y| Math.log(y) }
263
+ slnx2 = sigma(xs) { |x | Math.log(x) ** 2 }
264
+
265
+ b = (n * slnxlny - slnx * slny) / (n * slnx2 - slnx ** 2);
266
+ a = (slny - b * slnx) / n
267
+
268
+ return Math.exp(a), b, fit_error(xys) { |x| (Math.exp(a) * (x ** b)) }
269
+ end
270
+
271
+ ##
272
+ # Enumerates over +enum+ mapping +block+ if given, returning the
273
+ # sum of the result. Eg:
274
+ #
275
+ # sigma([1, 2, 3]) # => 1 + 2 + 3 => 7
276
+ # sigma([1, 2, 3]) { |n| n ** 2 } # => 1 + 4 + 9 => 14
277
+
278
+ def sigma enum, &block
279
+ enum = enum.map(&block) if block
280
+ enum.inject { |sum, n| sum + n }
281
+ end
282
+
283
+ ##
284
+ # Returns a proc that calls the specified fit method and asserts
285
+ # that the error is within a tolerable threshold.
286
+
287
+ def validation_for_fit msg, threshold
288
+ proc do |range, times|
289
+ a, b, rr = send "fit_#{msg}", range, times
290
+ assert_operator rr, :>=, threshold
291
+ [a, b, rr]
292
+ end
293
+ end
294
+ end
295
+ end
296
+
297
+ class MiniTest::Spec
298
+ def self.bench name, &block
299
+ define_method "bench_#{name.gsub(/\W+/, '_')}", &block
300
+ end
301
+
302
+ def self.bench_range &block
303
+ meta = (class << self; self; end)
304
+ meta.send :define_method, "bench_range", &block
305
+ end
306
+
307
+ def self.bench_performance_linear name, threshold = 0.9, &work
308
+ bench name do
309
+ assert_performance_linear threshold, &work
310
+ end
311
+ end
312
+
313
+ def self.bench_performance_constant name, threshold = 0.99, &work
314
+ bench name do
315
+ assert_performance_constant threshold, &work
316
+ end
317
+ end
318
+
319
+ def self.bench_performance_exponential name, threshold = 0.99, &work
320
+ bench name do
321
+ assert_performance_exponential threshold, &work
322
+ end
323
+ end
324
+ end