minitest 1.7.2 → 2.0.0

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.
@@ -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