lemon 0.8.4 → 0.8.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.config/cucumber.yml +3 -0
- data/.gitignore +8 -0
- data/.reap/digest +678 -0
- data/.reap/test.reap +7 -0
- data/.ruby +42 -45
- data/Assembly +43 -0
- data/HISTORY.rdoc +10 -0
- data/MANIFEST +65 -0
- data/PROFILE +9 -6
- data/Rakefile +14 -0
- data/VERSION +1 -1
- data/lemon.gemspec +152 -0
- data/lib/lemon.yml +42 -45
- data/lib/lemon/cli.rb +4 -2
- data/lib/lemon/controller/test_runner.rb +4 -0
- data/lib/lemon/model/test_case.rb +122 -40
- data/notes/2010-05-05-coverage.rdoc +47 -0
- data/notes/2010-05-06-files_not_classes.rdoc +19 -0
- data/notes/2010-07-11-acid_testing.rdoc +52 -0
- data/notes/2010-08-02-enforcing-the-unit.md +68 -0
- data/notes/2010-08-03-new-api.md +37 -0
- data/site/.rsync-filter +8 -0
- data/site/assets/images/cut-lemon.png +0 -0
- data/site/assets/images/forkme.png +0 -0
- data/site/assets/images/github-logo.png +0 -0
- data/site/assets/images/lemon.jpg +0 -0
- data/site/assets/images/lemon.svg +39 -0
- data/site/assets/images/lemons-are-good.png +0 -0
- data/site/assets/images/opensource.png +0 -0
- data/site/assets/images/ruby-logo.png +0 -0
- data/site/assets/images/skin.jpg +0 -0
- data/site/assets/images/skin1.jpg +0 -0
- data/site/assets/images/tap.png +0 -0
- data/site/assets/images/title.png +0 -0
- data/site/assets/styles/class.css +6 -0
- data/site/assets/styles/reset.css +17 -0
- data/site/assets/styles/site.css +33 -0
- data/site/index.html +217 -0
- data/work/deprecated/command/abstract.rb +29 -0
- data/work/deprecated/command/coverage.rb +115 -0
- data/work/deprecated/command/generate.rb +124 -0
- data/work/deprecated/command/test.rb +112 -0
- data/work/reference/dsl2.rb +136 -0
- data/work/reference/dynamic_constant_lookup.rb +76 -0
- data/work/sandbox/lib/sample.rb +13 -0
- data/work/sandbox/test/sample_case.rb +12 -0
- data/work/trash/example-cover.rb +5 -0
- data/work/trash/example.rb +16 -0
- metadata +134 -101
- data/.yardopts +0 -7
- data/QED.rdoc +0 -1
data/lib/lemon/cli.rb
CHANGED
@@ -61,7 +61,7 @@ module Lemon
|
|
61
61
|
def test(scripts)
|
62
62
|
require 'lemon/controller/test_runner'
|
63
63
|
|
64
|
-
loadpath = options[:loadpath] || []
|
64
|
+
loadpath = options[:loadpath] || ['lib'] # + ['lib'] ?
|
65
65
|
requires = options[:requires] || []
|
66
66
|
|
67
67
|
loadpath.each{ |path| $LOAD_PATH.unshift(path) }
|
@@ -74,7 +74,9 @@ module Lemon
|
|
74
74
|
scripts, :format=>options[:format], :namespaces=>options[:namespaces]
|
75
75
|
)
|
76
76
|
|
77
|
-
runner.run
|
77
|
+
success = runner.run
|
78
|
+
|
79
|
+
exit -1 unless success
|
78
80
|
end
|
79
81
|
|
80
82
|
#
|
@@ -77,6 +77,9 @@ module Lemon
|
|
77
77
|
end
|
78
78
|
|
79
79
|
# Run tests.
|
80
|
+
#
|
81
|
+
# @return [Boolean]
|
82
|
+
# Whether tests ran without error or failure.
|
80
83
|
def run
|
81
84
|
#prepare
|
82
85
|
report.start_suite(suite)
|
@@ -128,6 +131,7 @@ module Lemon
|
|
128
131
|
report.finish_case(testcase)
|
129
132
|
end
|
130
133
|
report.finish_suite(suite) #(successes, failures, errors, pendings)
|
134
|
+
return record[:error].size + record[:fail].size > 0 ? false : true
|
131
135
|
end
|
132
136
|
|
133
137
|
# Iterate over suite testcases, filtering out unselected testcases
|
@@ -21,11 +21,11 @@ module Lemon
|
|
21
21
|
# Ordered list of testunits.
|
22
22
|
attr :units
|
23
23
|
|
24
|
-
# Before
|
24
|
+
# Before matching test units.
|
25
25
|
attr :before
|
26
26
|
#attr_accessor :prepare
|
27
27
|
|
28
|
-
# After
|
28
|
+
# After matching test units.
|
29
29
|
attr :after
|
30
30
|
#attr_accessor :cleanup
|
31
31
|
|
@@ -88,6 +88,12 @@ module Lemon
|
|
88
88
|
end
|
89
89
|
|
90
90
|
# Define a unit test for this case.
|
91
|
+
#
|
92
|
+
# @example
|
93
|
+
# unit :puts => "print message with new line to stdout" do
|
94
|
+
# puts "Hello"
|
95
|
+
# end
|
96
|
+
#
|
91
97
|
def unit(*target, &block)
|
92
98
|
target = target.map{ |x| Hash === x ? x.to_a : x }.flatten
|
93
99
|
method, aspect = *target
|
@@ -138,10 +144,15 @@ module Lemon
|
|
138
144
|
end
|
139
145
|
alias_method :omit, :Omit
|
140
146
|
|
147
|
+
# Setup is used to set things up for each unit test.
|
148
|
+
# The setup procedure is run before each unit.
|
141
149
|
#
|
142
|
-
|
143
|
-
|
144
|
-
|
150
|
+
# @param [String] description
|
151
|
+
# A brief description of what the setup procedure sets-up.
|
152
|
+
#
|
153
|
+
def setup(description=nil, &procedure)
|
154
|
+
if procedure
|
155
|
+
context = TestContext.new(@testcase, description, &procedure)
|
145
156
|
@context = context
|
146
157
|
#@function = false
|
147
158
|
#@testcase.steps << context
|
@@ -150,60 +161,131 @@ module Lemon
|
|
150
161
|
alias_method :Setup, :setup
|
151
162
|
alias_method :Concern, :setup
|
152
163
|
alias_method :concern, :setup
|
164
|
+
|
165
|
+
# @deprecate This alias will probably not stick around.
|
153
166
|
alias_method :Context, :setup
|
154
167
|
alias_method :context, :setup
|
155
168
|
|
169
|
+
=begin
|
170
|
+
# TODO: Currently there is no difference between Setup, Instance and Singleton
|
171
|
+
|
156
172
|
## Define a new test instance for this case.
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
173
|
+
def instance(description=nil, &block)
|
174
|
+
if block
|
175
|
+
#context = TestInstance.new(@testcase, description, &block)
|
176
|
+
context = TestContext.new(@testcase, description, &block)
|
177
|
+
else
|
178
|
+
context = TestContext.new(@testcase, description) do
|
179
|
+
@testcase.target.new # No arguments!!!
|
180
|
+
end
|
181
|
+
end
|
182
|
+
@context = context
|
183
|
+
#@function = false
|
184
|
+
#@testcase.steps << context
|
185
|
+
end
|
186
|
+
alias_method :Instance, :instance
|
164
187
|
|
165
188
|
# Define a new test singleton for this case.
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
189
|
+
def Singleton(description=nil, &block)
|
190
|
+
if block
|
191
|
+
#context = TestSingleton.new(@testcase, description, &block)
|
192
|
+
context = TestContext.new(@testcase, description, &block)
|
193
|
+
else
|
194
|
+
context = TestContext.new(@testcase, description){ @testcase.target }
|
195
|
+
end
|
196
|
+
@context = context
|
197
|
+
#@function = true
|
198
|
+
#@testcase.steps << context
|
199
|
+
end
|
200
|
+
alias_method :singleton, :Singleton
|
201
|
+
=end
|
173
202
|
|
174
|
-
|
175
|
-
|
203
|
+
# Teardown procedure is used to clean-up after each unit test.
|
204
|
+
def teardown(&procedure)
|
205
|
+
@context.teardown = procedure
|
176
206
|
end
|
177
207
|
alias_method :Teardown, :teardown
|
178
208
|
|
179
209
|
# TODO: Make Before and After more generic to handle before and after
|
180
|
-
# units, contexts/concerns, etc
|
181
|
-
|
182
|
-
# Define a before procedure
|
183
|
-
|
184
|
-
|
210
|
+
# units, contexts/concerns, etc?
|
211
|
+
|
212
|
+
# Define a _complex_ before procedure. The #before method allows
|
213
|
+
# before procedures to be defined that are triggered by a match
|
214
|
+
# against the unit's target method name or _aspect_ description.
|
215
|
+
# This allows groups of tests to be defined that share special
|
216
|
+
# setup code.
|
217
|
+
#
|
218
|
+
# @example
|
219
|
+
#
|
220
|
+
# unit :puts => "standard output (@stdout)" do
|
221
|
+
# puts "Hello"
|
222
|
+
# end
|
223
|
+
#
|
224
|
+
# before /@stdout/ do
|
225
|
+
# $stdout = StringIO.new
|
226
|
+
# end
|
227
|
+
#
|
228
|
+
# after /@stdout/ do
|
229
|
+
# $stdout = STDOUT
|
230
|
+
# end
|
231
|
+
#
|
232
|
+
# @param [Array<Symbol,Regexp>] matches
|
233
|
+
# List of match critera that must _all_ be matched
|
234
|
+
# to trigger the before procedure.
|
235
|
+
#
|
236
|
+
def before(*matches, &procedure)
|
237
|
+
@testcase.before[matches] = procedure
|
185
238
|
end
|
186
239
|
alias_method :Before, :before
|
187
240
|
|
188
|
-
# Define
|
189
|
-
|
190
|
-
|
241
|
+
# Define a _complex_ after procedure. The #before method allows
|
242
|
+
# before procedures to be defined that are triggered by a match
|
243
|
+
# against the unit's target method name or _aspect_ description.
|
244
|
+
# This allows groups of tests to be defined that share special
|
245
|
+
# teardown code.
|
246
|
+
#
|
247
|
+
# @example
|
248
|
+
#
|
249
|
+
# unit :puts => "standard output (@stdout)" do
|
250
|
+
# puts "Hello"
|
251
|
+
# end
|
252
|
+
#
|
253
|
+
# before /@stdout/ do
|
254
|
+
# $stdout = StringIO.new
|
255
|
+
# end
|
256
|
+
#
|
257
|
+
# after /@stdout/ do
|
258
|
+
# $stdout = STDOUT
|
259
|
+
# end
|
260
|
+
#
|
261
|
+
# @param [Array<Symbol,Regexp>] matches
|
262
|
+
# List of match critera that must _all_ be matched
|
263
|
+
# to trigger the after procedure.
|
264
|
+
#
|
265
|
+
def after(*matches, &procedure)
|
266
|
+
@testcase.after[matches] = procedure
|
191
267
|
end
|
192
268
|
alias_method :After, :after
|
193
269
|
|
194
|
-
#
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
270
|
+
# Define a "before all" procedure.
|
271
|
+
def prepare(&procedure)
|
272
|
+
before(&procedure)
|
273
|
+
end
|
274
|
+
alias_method :Prepare, :prepare
|
199
275
|
|
200
|
-
#
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
276
|
+
# Define an "after all" procedure.
|
277
|
+
def cleanup(&procedure)
|
278
|
+
after(&procedure)
|
279
|
+
end
|
280
|
+
alias_method :Cleanup, :cleanup
|
205
281
|
|
206
|
-
# Load a helper script applicable to this test case.
|
282
|
+
# Load a helper script applicable to this test case. Unlike requiring
|
283
|
+
# a helper script, the #helper method will eval the file's contents
|
284
|
+
# directly into the test context (using instance_eval).
|
285
|
+
#
|
286
|
+
# @param [String] file
|
287
|
+
# File to eval into test context.
|
288
|
+
#
|
207
289
|
def helper(file)
|
208
290
|
instance_eval(File.read(file), file)
|
209
291
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
= 2010-05-05 Getting Good Coverage
|
2
|
+
|
3
|
+
Getting good coverage analysis is not easy. The difficulty comes
|
4
|
+
is determining what is of interest to the tests and what
|
5
|
+
is not. That isn't so hard with completely new code, but
|
6
|
+
core extensions, for instance, are challenging to isolate
|
7
|
+
from original methods.
|
8
|
+
|
9
|
+
Thee issue can mostly be taken care of by taking a canonical
|
10
|
+
snapshot of the system before loading the tests. Then taking
|
11
|
+
a snapshot after loading the tests and comparing the two.
|
12
|
+
For the most part the difference will be the code in need of
|
13
|
+
coverage.
|
14
|
+
|
15
|
+
Unfortunately helper code still gets in the way of taking these
|
16
|
+
snapshots. As a result I have determined there are only four
|
17
|
+
possible complete solutions.
|
18
|
+
|
19
|
+
1) Use a Ruby lexer/parser to syntatically deconstruct the
|
20
|
+
target libraries. This is a complex solution, and not one
|
21
|
+
I relish trying.
|
22
|
+
|
23
|
+
2) All helpers must go in special files that are preloaded so
|
24
|
+
the snapshot can isolate it from the actual target code. This
|
25
|
+
means any mocks, for instance, would have to be defined in
|
26
|
+
separate files for the the test(s) that might use them. This
|
27
|
+
solution it good in that it actually encourges the writing
|
28
|
+
reusable helper code, but it can be annoying when writting
|
29
|
+
one-off mocks and such.
|
30
|
+
|
31
|
+
3) As an alternate to #2, Lemon could provide a method
|
32
|
+
for specifying that a class/module or method is a helper
|
33
|
+
and can simply be ignored when analysing coverage.
|
34
|
+
|
35
|
+
4) Lastly, the library file being covered can be specified
|
36
|
+
with a special method, e.g. #Covers. The method would act
|
37
|
+
just like +#require+ except that it takes a snapshot before and
|
38
|
+
after the loading the library, and in this way builds up
|
39
|
+
a table of proper coverage targets.
|
40
|
+
|
41
|
+
Options #2 and #3 have proven lack luster. It's simply too
|
42
|
+
much overhead involved to get good coverage. Option #4 is the most
|
43
|
+
robust choice. Unfortunately coverage reports can be very slow
|
44
|
+
if there are lot of files to cover --Snapshots can take a second
|
45
|
+
or two to create. Hopefully it can be sped up with refinement,
|
46
|
+
but at the very least the coverage reporting if optional.
|
47
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
= 2010-05-06 Files, Not Classes
|
2
|
+
|
3
|
+
Just had a realization about my approch to Lemon tests.
|
4
|
+
Until now I have equated the test-case to the class/module
|
5
|
+
and the test-unit to the method. That makes sense, but it means
|
6
|
+
I have left out a very important factor in the the design
|
7
|
+
of tests: the file.
|
8
|
+
|
9
|
+
I had defined a test-suite as the entire set of tests to
|
10
|
+
be run. But I think now I am mistaken. The suite should
|
11
|
+
correspond to the fiel being tested. Often that will mean
|
12
|
+
one test-case per test-suite, but that's okay.
|
13
|
+
|
14
|
+
After the next release, 0.7.0, I will implement this
|
15
|
+
refactorization. I beleive it should ultimately help
|
16
|
+
improve test coverage as well.
|
17
|
+
|
18
|
+
|
19
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
= 2010-07-11 | Kool-Aid
|
2
|
+
|
3
|
+
Taking Unit Testing to the next level with a dab of DBC.
|
4
|
+
|
5
|
+
Say we have,
|
6
|
+
|
7
|
+
class K
|
8
|
+
|
9
|
+
def f1(a1)
|
10
|
+
g1(a1)
|
11
|
+
end
|
12
|
+
|
13
|
+
def g1(a1)
|
14
|
+
a1 + 1
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
Now what do we want to say about K#f1?
|
20
|
+
|
21
|
+
unit K, :f1 do |a|
|
22
|
+
case a
|
23
|
+
when 1
|
24
|
+
result do |r|
|
25
|
+
r.assert == 2
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Now later we induce labor.
|
31
|
+
|
32
|
+
k = K.new
|
33
|
+
k.f1(1)
|
34
|
+
|
35
|
+
It does't really matter what the result class contains, or how it
|
36
|
+
even gets called. Thus it can test side-effects as well as simple
|
37
|
+
functional results.
|
38
|
+
|
39
|
+
Okay, so how do we implement this?
|
40
|
+
|
41
|
+
class K
|
42
|
+
|
43
|
+
alias "f1:lemon", :f1
|
44
|
+
|
45
|
+
def f1(a1)
|
46
|
+
Lemon.verify_unit(binding) do
|
47
|
+
send("f1:lemon", a1)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
2010-08-02 | Enforcing the Unit
|
2
|
+
|
3
|
+
The current API for creating a Lemon unit testcase looks like this:
|
4
|
+
|
5
|
+
covers 'someclass'
|
6
|
+
|
7
|
+
testcase SomeClass do
|
8
|
+
|
9
|
+
unit :some_method => "does something or another" do
|
10
|
+
# ...
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
As it currentlt standa the current design is little more than a means of
|
16
|
+
organization, orienting the developer to think in terms of test units.
|
17
|
+
What is does not do is enforce the actual testing the the unit referenced.
|
18
|
+
We could put any old mess in the unit block and as long as it did not raise
|
19
|
+
an exception, it would get a *pass*.
|
20
|
+
|
21
|
+
Taking some time to consider this in depth, I've concieved of a way in which
|
22
|
+
that use of the method could in fact be enforced.
|
23
|
+
|
24
|
+
covers 'someclass'
|
25
|
+
|
26
|
+
testcase SomeClass do
|
27
|
+
|
28
|
+
setup do
|
29
|
+
SomeClass.new
|
30
|
+
end
|
31
|
+
|
32
|
+
unit :some_method => "does something or another" do |unit|
|
33
|
+
unit.object # object from setup
|
34
|
+
unit.call(...) # calls #some_method on unit.object
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
What is intersting about this, beyond that fact that it enforces the use of
|
40
|
+
the class or module and method involved, but that it also does so in
|
41
|
+
a way naturally suited to mocking --the `unit` delegator could even have
|
42
|
+
mocking methods built-in.
|
43
|
+
|
44
|
+
unit :some_method => "does something or another" do |unit|
|
45
|
+
unit.receives.foo(:bar) # object from setup would receive this call
|
46
|
+
unit.returns(:baz) # the subsequent #call will return this
|
47
|
+
unit.call(...) # calls #some_method on unit.object
|
48
|
+
end
|
49
|
+
|
50
|
+
On the downside this approach limits what can be done in the unit block.
|
51
|
+
One _has_ to utilize the object as defined in `setup` and one _has_ to invoke
|
52
|
+
the unit method via the `#call` interface. Though, I suppose one could argue
|
53
|
+
that these limitations are a good thing, as they help the unit stay narrowly
|
54
|
+
focused on that goal at hand.
|
55
|
+
|
56
|
+
I think this approach is worth considering for a possible furture version.
|
57
|
+
Perhaps a "Lemon 2.0". For the time being I believe we can enforce the unit
|
58
|
+
without resorting this major API change.
|
59
|
+
|
60
|
+
The next release of Lemon will temporarily override the unit method on the
|
61
|
+
target class for each unit execution. If the unit method gets called within
|
62
|
+
the unit block, then it will be noted by the overridden method before passing
|
63
|
+
off to the original definition. The approach is perhaps a bit draconian, and
|
64
|
+
is certainly only possible thanks to the remarkable dynamicism of Ruby, but
|
65
|
+
it should work perfectly well. So now, if the target method is not called within
|
66
|
+
the taget block, the unit will raise a Pending exception, regardless of the
|
67
|
+
code in the block. Unit Enforced!
|
68
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# 2010-08-03 | A New API
|
2
|
+
|
3
|
+
Simplified API. There is one main method.
|
4
|
+
|
5
|
+
unit_test SomeClass, :some_method, "description" do
|
6
|
+
# test ...
|
7
|
+
end
|
8
|
+
|
9
|
+
It will be a global method. The block notations would still work, but
|
10
|
+
they would simple become wrappers for the main method.
|
11
|
+
|
12
|
+
testcase SomeClass do
|
13
|
+
|
14
|
+
unit :some_method, "description" do
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
If I can make it backward compatible, I may also allow something like:
|
21
|
+
|
22
|
+
testcase SomeClass do
|
23
|
+
|
24
|
+
unit :some_method do
|
25
|
+
|
26
|
+
concern "description" do
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
By using this global method, I should be able to simplify the underlying
|
35
|
+
implementation a great deal, which has been major concern about Lemon
|
36
|
+
as of late.
|
37
|
+
|