SlimTest 4.6.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +21 -0
- data/.gemtest +0 -0
- data/History.txt +795 -0
- data/Manifest.txt +38 -0
- data/README.txt +107 -0
- data/Rakefile +52 -0
- data/articles/Article.css +721 -0
- data/articles/getting_started_with_autotest.html +533 -0
- data/articles/how_to_use_zentest.txt +393 -0
- data/bin/slim-autotest +6 -0
- data/bin/slim-multigem +4 -0
- data/bin/slim-multiruby +76 -0
- data/bin/slim-multiruby_setup +74 -0
- data/bin/slim-unit_diff +38 -0
- data/bin/slim-zentest +23 -0
- data/example.txt +42 -0
- data/example1.rb +7 -0
- data/example2.rb +15 -0
- data/example_dot_autotest.rb +16 -0
- data/lib/autotest.rb +852 -0
- data/lib/autotest/autoupdate.rb +26 -0
- data/lib/autotest/bundler.rb +10 -0
- data/lib/autotest/isolate.rb +19 -0
- data/lib/autotest/once.rb +9 -0
- data/lib/autotest/preload.rb +56 -0
- data/lib/autotest/rcov.rb +27 -0
- data/lib/autotest/restart.rb +14 -0
- data/lib/autotest/timestamp.rb +9 -0
- data/lib/focus.rb +25 -0
- data/lib/functional_test_matrix.rb +92 -0
- data/lib/multiruby.rb +412 -0
- data/lib/unit_diff.rb +274 -0
- data/lib/zentest.rb +594 -0
- data/lib/zentest_mapping.rb +117 -0
- data/test/test_autotest.rb +527 -0
- data/test/test_focus.rb +35 -0
- data/test/test_unit_diff.rb +372 -0
- data/test/test_zentest.rb +566 -0
- data/test/test_zentest_mapping.rb +242 -0
- metadata +151 -0
@@ -0,0 +1,393 @@
|
|
1
|
+
How to Use ZenTest with Ruby
|
2
|
+
by Pat Eyler <pate@kohalabs.com>
|
3
|
+
http://linuxjournal.com/article.php?sid=7776
|
4
|
+
(included in this package with permission)
|
5
|
+
|
6
|
+
Refactoring and unit testing are a great pair of tools for every
|
7
|
+
programmer's workbench. Sadly, not every programmer knows how to use
|
8
|
+
them. My first exposure to them came when I started using Ruby,
|
9
|
+
refactoring and unit testing are a big part of the landscape in the
|
10
|
+
Ruby community.
|
11
|
+
|
12
|
+
Some time ago, I translated the refactoring example from the first
|
13
|
+
chapter of Martin Fowler's excellent book, Refactoring, out of Java
|
14
|
+
and into Ruby. I felt this would be a great way to learn more about
|
15
|
+
refactoring and brush up on my Ruby while I was at it. Recently, I
|
16
|
+
decided to update the translation for Ruby 1.8.X. One of the things I
|
17
|
+
needed to change was to convert the old unit tests to work with
|
18
|
+
Test::Unit, the new unit testing framework for Ruby.
|
19
|
+
|
20
|
+
I wasn't really looking forward to building a new test suite though.
|
21
|
+
Fortunately, help was available. Ryan Davis has written a great tool
|
22
|
+
called ZenTest, which creates test suites for existing bodies of
|
23
|
+
code. Since a lot of people are new to refactoring, unit testing, and
|
24
|
+
ZenTest, I thought this would be a great chance to introduce you to
|
25
|
+
this trio of tools.
|
26
|
+
|
27
|
+
Martin's example code is built around a video store application. In
|
28
|
+
his original code, there are three classes; Customer, Movie, and
|
29
|
+
Rental. I'll focus on just the Customer class in this article.
|
30
|
+
Here's the original code:
|
31
|
+
|
32
|
+
class Customer
|
33
|
+
attr_accessor :name
|
34
|
+
|
35
|
+
def initialize(name)
|
36
|
+
@name = name
|
37
|
+
@rentals = Array.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def addRental(aRental)
|
41
|
+
@rentals.push(aRental)
|
42
|
+
end
|
43
|
+
|
44
|
+
def statement
|
45
|
+
totalAmount = 0.0
|
46
|
+
frequentRenterPoints = 0
|
47
|
+
rentals = @rentals.length
|
48
|
+
result = "\nRental Record for #{@name}\n"
|
49
|
+
thisAmount = 0.0
|
50
|
+
@rentals.each do |rental|
|
51
|
+
# determine amounts for each line
|
52
|
+
case rental.aMovie.pricecode
|
53
|
+
when Movie::REGULAR
|
54
|
+
thisAmount += 2
|
55
|
+
if rental.daysRented > 2
|
56
|
+
thisAmount += (rental.daysRented - 2) * 1.5
|
57
|
+
end
|
58
|
+
|
59
|
+
when Movie::NEW_RELEASE
|
60
|
+
thisAmount += rental.daysRented * 3
|
61
|
+
|
62
|
+
when Movie::CHILDRENS
|
63
|
+
thisAmount += 1.5
|
64
|
+
if each.daysRented > 3
|
65
|
+
thisAmount += (rental.daysRented - 3) * 1.5
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
# add frequent renter points
|
71
|
+
frequentRenterPoints += 1
|
72
|
+
# add bonus for a two day new release rental
|
73
|
+
if ( rental.daysRented > 1) &&
|
74
|
+
(Movie::NEW_RELEASE == rental.aMovie.pricecode)
|
75
|
+
frequentRenterPoints += 1
|
76
|
+
end
|
77
|
+
|
78
|
+
# show figures for this rental
|
79
|
+
result +="\t#{rental.aMovie.title}\t#{thisAmount}\n"
|
80
|
+
totalAmount += thisAmount
|
81
|
+
end
|
82
|
+
result += "Amount owed is #{totalAmount}\n"
|
83
|
+
result += "You earned #{frequentRenterPoints} frequent renter points"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
Not the cleanest code in the world, but it is supposed to be that
|
89
|
+
way. This represents the code as you get it from the user. No
|
90
|
+
tests, poorly laid out, but working -- and it's your job to make it
|
91
|
+
better without breaking it. So, where to start? With unit tests of
|
92
|
+
course.
|
93
|
+
|
94
|
+
Time to grab ZenTest. You can run it like this:
|
95
|
+
|
96
|
+
$ zentest videostore.rb > test_videostore.rb
|
97
|
+
|
98
|
+
which produces a file full of tests. Running the test suite doesn't
|
99
|
+
do quite what we were hoping though:
|
100
|
+
|
101
|
+
$ ruby testVideoStore.rb Loaded suite testVideoStore
|
102
|
+
Started
|
103
|
+
EEEEEEEEEEE
|
104
|
+
Finished in 0.008974 seconds.
|
105
|
+
|
106
|
+
1) Error!!!
|
107
|
+
test_addRental(TestCustomer):
|
108
|
+
NotImplementedError: Need to write test_addRental
|
109
|
+
testVideoStore.rb:11:in `test_addRental'
|
110
|
+
testVideoStore.rb:54
|
111
|
+
|
112
|
+
2) Error!!!
|
113
|
+
test_name=(TestCustomer):
|
114
|
+
NotImplementedError: Need to write test_name=
|
115
|
+
testVideoStore.rb:15:in `test_name='
|
116
|
+
testVideoStore.rb:54
|
117
|
+
|
118
|
+
3) Error!!!
|
119
|
+
test_statement=(TestCustomer):
|
120
|
+
NotImplementedError: Need to write test_statement
|
121
|
+
testVideoStore.rb:19:in `test_statement'
|
122
|
+
testVideoStore.rb:54
|
123
|
+
.
|
124
|
+
.
|
125
|
+
.
|
126
|
+
|
127
|
+
11 tests, 0 assertions, 0 failures, 11 errors
|
128
|
+
$
|
129
|
+
|
130
|
+
So what exactly did we get out of this? Here's the portion of our
|
131
|
+
new test suite that matters for the Customer class:
|
132
|
+
|
133
|
+
# Code Generated by ZenTest v. 2.1.2
|
134
|
+
# classname: asrt / meth = ratio%
|
135
|
+
# Customer: 0 / 3 = 0.00%
|
136
|
+
|
137
|
+
require 'test/unit'
|
138
|
+
|
139
|
+
class TestCustomer < Test::Unit::TestCase
|
140
|
+
def test_addRental
|
141
|
+
raise NotImplementedError, 'Need to write test_addRental'
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_name=
|
145
|
+
raise NotImplementedError, 'Need to write test_name='
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_statement
|
149
|
+
raise NotImplementedError, 'Need to write test_statement'
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
ZenTest built three test methods: one for the accessor method, one for
|
154
|
+
the addRental method, and one for the statement method. Why nothing
|
155
|
+
for the initializer? Well, initializers tend to be pretty bulletproof
|
156
|
+
(if they're not, it's pretty easy to add the test method yourself).
|
157
|
+
Besides, we'll be testing it indirectly when we write test_name= (the
|
158
|
+
tests for the accessor method). There's one other thing we'll need to
|
159
|
+
add, the test suite doesn't load the code we're testing. Changing the
|
160
|
+
beginning of the script to require the videostore.rb file will do the
|
161
|
+
trick for us.
|
162
|
+
|
163
|
+
|
164
|
+
# Code Generated by ZenTest v. 2.1.2
|
165
|
+
# classname: asrt / meth = ratio%
|
166
|
+
# Customer: 0 / 3 = 0.00%
|
167
|
+
|
168
|
+
require 'test/unit'
|
169
|
+
require 'videostore'
|
170
|
+
|
171
|
+
That little snippet of comments at the top lets us know that we have three
|
172
|
+
methods under test in the Customer class, zero assertions testing
|
173
|
+
them, and no coverage. Let's fix that. We'll start by writing some
|
174
|
+
tests for test_name= (no, it really doesn't matter what order we go in --
|
175
|
+
this is just a convenient place to start).
|
176
|
+
|
177
|
+
def test_name=
|
178
|
+
aCustomer = Customer.new("Fred Jones")
|
179
|
+
assert_equal("Fred Jones",aCustomer.name)
|
180
|
+
aCustomer.name = "Freddy Jones"
|
181
|
+
assert_equal("Freddy Jones",aCustomer.name
|
182
|
+
end
|
183
|
+
|
184
|
+
Running testVideoStore.rb again gives us:
|
185
|
+
|
186
|
+
$ ruby testVideoStore.rb
|
187
|
+
Loaded suite testVideoStore
|
188
|
+
Started
|
189
|
+
E.EEEEEEEEE
|
190
|
+
Finished in 0.011233 seconds.
|
191
|
+
|
192
|
+
1) Error!!!
|
193
|
+
test_addRental(TestCustomer):
|
194
|
+
NotImplementedError: Need to write test_addRental
|
195
|
+
testVideoStore.rb:13:in `test_addRental'
|
196
|
+
testVideoStore.rb:58
|
197
|
+
|
198
|
+
2) Error!!!
|
199
|
+
test_statement(TestCustomer):
|
200
|
+
NotImplementedError: Need to write test_statement
|
201
|
+
testVideoStore.rb:23:in `test_statement'
|
202
|
+
testVideoStore.rb:58
|
203
|
+
.
|
204
|
+
.
|
205
|
+
.
|
206
|
+
11 tests, 2 assertions, 0 failures, 10 errors
|
207
|
+
$
|
208
|
+
|
209
|
+
So far, so good. The line of 'E's (which shows errors in the test run)
|
210
|
+
has been reduced by one, and the summary line at the bottom tells us
|
211
|
+
roughly the same thing.
|
212
|
+
|
213
|
+
We really don't have a way to test addRental directly, so we'll just
|
214
|
+
write an stub test for now.
|
215
|
+
|
216
|
+
def test_addRental
|
217
|
+
assert(1) # stub test, since there is nothing in the method to test
|
218
|
+
end
|
219
|
+
|
220
|
+
When we run the tests again, we get:
|
221
|
+
|
222
|
+
$ ruby testVideoStore.rb
|
223
|
+
Loaded suite testVideoStore
|
224
|
+
Started
|
225
|
+
..EEEEEEEEE
|
226
|
+
Finished in 0.008682 seconds.
|
227
|
+
|
228
|
+
1) Error!!!
|
229
|
+
test_statement(TestCustomer):
|
230
|
+
NotImplementedError: Need to write test_statement
|
231
|
+
testVideoStore.rb:22:in `test_statement'
|
232
|
+
testVideoStore.rb:57
|
233
|
+
.
|
234
|
+
.
|
235
|
+
.
|
236
|
+
11 tests, 3 assertions, 0 failures, 9 errors
|
237
|
+
$
|
238
|
+
|
239
|
+
Better and better, just one error left in the TestCustomer class.
|
240
|
+
Let's finish up with a test that will clear our test_statement error
|
241
|
+
and verify that addRental works correctly:
|
242
|
+
|
243
|
+
def test_statement
|
244
|
+
aMovie = Movie.new("Legacy",0)
|
245
|
+
|
246
|
+
aRental = Rental.new(aMovie,2)
|
247
|
+
|
248
|
+
aCustomer = Customer.new("Fred Jones")
|
249
|
+
aCustomer.addRental(aRental)
|
250
|
+
aStatement = "\nRental Record for Fred Jones\n\tLegacy\t2.0
|
251
|
+
Amount owed is 2.0\nYou earned 1 frequent renter points"
|
252
|
+
|
253
|
+
assert_equal(aStatement,aCustomer.statement)
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
We run the tests again, and see:
|
258
|
+
|
259
|
+
$ ruby testVideoStore.rb
|
260
|
+
Loaded suite testVideoStore
|
261
|
+
Started
|
262
|
+
...EEEEEEEE
|
263
|
+
Finished in 0.009378 seconds.
|
264
|
+
.
|
265
|
+
.
|
266
|
+
.
|
267
|
+
11 tests, 4 assertions, 0 failures, 8 errors
|
268
|
+
$
|
269
|
+
|
270
|
+
Great! The only errors left are on the Movie and Rental classes,
|
271
|
+
the Customer class is clean.
|
272
|
+
|
273
|
+
We can continue along like this for the remaining classes, but I'll
|
274
|
+
not bore you with those details. Instead, I'd like to look at how
|
275
|
+
ZenTest can help when you've already got some tests in place. Later
|
276
|
+
development allows us to do just that -- the video store owner
|
277
|
+
wants a new web based statement for web using customers.
|
278
|
+
|
279
|
+
After a bit of refactoring and new development, the code looks like
|
280
|
+
this:
|
281
|
+
|
282
|
+
class Customer
|
283
|
+
attr_accessor :name
|
284
|
+
|
285
|
+
def initialize(name)
|
286
|
+
@name = name
|
287
|
+
@rentals = Array.new
|
288
|
+
end
|
289
|
+
|
290
|
+
def addRental(aRental)
|
291
|
+
@rentals.push(aRental)
|
292
|
+
end
|
293
|
+
|
294
|
+
def statement
|
295
|
+
result = "\nRental Record for #{@name}\n"
|
296
|
+
@rentals.each do
|
297
|
+
|each|
|
298
|
+
# show figures for this rental
|
299
|
+
result +="\t#{each.aMovie.title}\t#{each.getCharge}\n"
|
300
|
+
end
|
301
|
+
result += "Amount owed is #{getTotalCharge}\n"
|
302
|
+
result +=
|
303
|
+
"You earned #{getFrequentRenterPoints} frequent renter points"
|
304
|
+
end
|
305
|
+
|
306
|
+
def htmlStatement
|
307
|
+
result = "\n<H1>Rentals for <EM>#{name}</EM></H1><P>\n"
|
308
|
+
@rentals.each do
|
309
|
+
|each|
|
310
|
+
result += "#{each.aMovie.title}: #{each.getCharge}<BR>\n"
|
311
|
+
end
|
312
|
+
result += "You owe <EM>#{getTotalCharge}</EM><P>\n"
|
313
|
+
result +=
|
314
|
+
"On this rental you earned <EM>#{getFrequentRenterPoints}" +
|
315
|
+
"</EM> frequent renter points<P>"
|
316
|
+
end
|
317
|
+
|
318
|
+
def getTotalCharge
|
319
|
+
result = 0.0
|
320
|
+
@rentals.each do
|
321
|
+
|each|
|
322
|
+
result += each.getCharge()
|
323
|
+
end
|
324
|
+
result
|
325
|
+
end
|
326
|
+
|
327
|
+
def getFrequentRenterPoints
|
328
|
+
result = 0
|
329
|
+
@rentals.each do
|
330
|
+
|each|
|
331
|
+
result += each.getFrequentRenterPoints
|
332
|
+
end
|
333
|
+
result
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
There's a lot of new stuff in here. If we run ZenTest again, it'll
|
338
|
+
pick up the methods we don't have any coverage on (we should have
|
339
|
+
written them as we wrote the new methods, but this is a bit more
|
340
|
+
illustrative). This time, we'll invoke ZenTest a little bit
|
341
|
+
differently:
|
342
|
+
|
343
|
+
$ zentest videostore.rb testVideoStore.rb > Missing_tests
|
344
|
+
|
345
|
+
and our (trimmed) output looks like this:
|
346
|
+
|
347
|
+
# Code Generated by ZenTest v. 2.1.2
|
348
|
+
# classname: asrt / meth = ratio%
|
349
|
+
# Customer: 4 / 6 = 66.67%
|
350
|
+
|
351
|
+
|
352
|
+
require 'test/unit'
|
353
|
+
|
354
|
+
class TestCustomer < Test::Unit::TestCase
|
355
|
+
def test_getFrequentRenterPoints
|
356
|
+
raise NotImplementedError,
|
357
|
+
'Need to write test_getFrequentRenterPoints'
|
358
|
+
end
|
359
|
+
|
360
|
+
def test_getTotalCharge
|
361
|
+
raise NotImplementedError, 'Need to write test_getTotalCharge'
|
362
|
+
end
|
363
|
+
|
364
|
+
def test_htmlStatement
|
365
|
+
raise NotImplementedError, 'Need to write test_htmlStatement'
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
Hmmm, three more test methods to fill in to get our complete
|
370
|
+
coverage. As we write these, we can just migrate them into our
|
371
|
+
existing testVideoStore.rb test suite. Then we can keep moving ahead
|
372
|
+
with refactoring and adding new features. In the future, let's just
|
373
|
+
be sure we add tests as we go along. ZenTest can help you here too.
|
374
|
+
You can write stubs for new development, then run ZenTest to create
|
375
|
+
your new test stubs as well. After some refactorings (like 'extract
|
376
|
+
method'), ZenTest can be used the same way.
|
377
|
+
|
378
|
+
Refactoring and unit testing are powerful tools for programmers, and
|
379
|
+
ZenTest provides an easy way to start using them in a Ruby
|
380
|
+
environment. Hopefully, this introduction has whetted your appetite.
|
381
|
+
|
382
|
+
If you're interested in learning more about refactoring, please grab a
|
383
|
+
copy of 'Refactoring: Improving the Design of Existing Code' and take
|
384
|
+
a look at www.refactoring.com. For more information about unit
|
385
|
+
testing, please see: c2.com/cgi/wiki?UnitTest,
|
386
|
+
www.junit.org/index.htm, and
|
387
|
+
www.extremeprogramming.org/rules/unittests.html.
|
388
|
+
|
389
|
+
The latest information about Test::Unit and ZenTest are available at
|
390
|
+
their home pages: testunit.talbott.ws (for Test::Unit) and
|
391
|
+
www.zenspider.com/ZSS/Products/ZenTest.
|
392
|
+
|
393
|
+
|
data/bin/slim-autotest
ADDED
data/bin/slim-multigem
ADDED
data/bin/slim-multiruby
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require 'multiruby'
|
4
|
+
|
5
|
+
root_dir = Multiruby.root_dir
|
6
|
+
|
7
|
+
def setenv dir
|
8
|
+
bin = "#{dir}/bin"
|
9
|
+
gem = Dir["#{dir}/lib/ruby/gems/*"].first
|
10
|
+
|
11
|
+
ENV['PATH'] = bin + File::PATH_SEPARATOR + ENV['PATH']
|
12
|
+
ENV['GEM_HOME'] = gem
|
13
|
+
ENV['GEM_PATH'] = gem
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# multiruby -1 1.8.7 ruby_args...
|
18
|
+
|
19
|
+
if ARGV.first == "-1" then
|
20
|
+
ARGV.shift
|
21
|
+
vers = Dir["#{root_dir}/install/#{ARGV.shift}*"]
|
22
|
+
|
23
|
+
abort "ambiguous version: #{vers.map { |p| File.basename p }.inspect}" if
|
24
|
+
vers.size != 1
|
25
|
+
|
26
|
+
dir = vers.first
|
27
|
+
setenv dir
|
28
|
+
|
29
|
+
exec "#{dir}/bin/ruby", *ARGV
|
30
|
+
end
|
31
|
+
|
32
|
+
versions = Multiruby.build_and_install
|
33
|
+
versions = ENV['VERSIONS'].split(/:/) if ENV.has_key? 'VERSIONS'
|
34
|
+
|
35
|
+
if ENV.has_key? 'EXCLUDED_VERSIONS' then
|
36
|
+
excludes = Regexp.union(*ENV['EXCLUDED_VERSIONS'].split(/:/))
|
37
|
+
versions = versions.delete_if { |v| v =~ excludes }
|
38
|
+
end
|
39
|
+
|
40
|
+
# safekeep original PATH
|
41
|
+
original_path = ENV['PATH']
|
42
|
+
|
43
|
+
results = {}
|
44
|
+
versions.each do |version|
|
45
|
+
dir = "#{root_dir}/install/#{version}"
|
46
|
+
ruby = "#{dir}/bin/ruby"
|
47
|
+
|
48
|
+
ruby.sub!(/bin.ruby/, 'bin/rbx') if version =~ /rubinius/
|
49
|
+
|
50
|
+
puts
|
51
|
+
puts "VERSION = #{version}"
|
52
|
+
cmd = [ruby, ARGV].flatten.map { |s| s =~ /\"/ ? "'#{s}'" : s }.join(' ')
|
53
|
+
cmd.sub!(/#{ENV['HOME']}/, '~')
|
54
|
+
puts "CMD = #{cmd}"
|
55
|
+
puts
|
56
|
+
|
57
|
+
setenv dir
|
58
|
+
|
59
|
+
system ruby, *ARGV
|
60
|
+
puts
|
61
|
+
puts "RESULT = #{$?}"
|
62
|
+
results[version] = $?
|
63
|
+
|
64
|
+
# restore the path to original state
|
65
|
+
ENV['PATH'] = original_path
|
66
|
+
end
|
67
|
+
|
68
|
+
passed, failed = results.keys.partition { |v| results[v] == 0 }
|
69
|
+
|
70
|
+
puts
|
71
|
+
puts "TOTAL RESULT = #{failed.size} failures out of #{results.size}"
|
72
|
+
puts
|
73
|
+
puts "Passed: #{passed.join(", ")}"
|
74
|
+
puts "Failed: #{failed.join(", ")}"
|
75
|
+
|
76
|
+
exit failed.size
|