speccify 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +8 -4
- data/lib/speccify.rb +155 -41
- metadata +4 -4
data/README.markdown
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
# Speccify
|
2
2
|
|
3
|
-
##
|
3
|
+
## The lightweight option.
|
4
4
|
|
5
|
+
[Website](http://speccify.rubyforge.org)
|
6
|
+
[Lighthouse](http://300.lighthouseapp.com/projects/24443/home)
|
7
|
+
[GoogleGroup](http://groups.google.de/group/speccify)
|
8
|
+
[RDoc](http://speccify.rubyforge.org/doc/index.html)
|
5
9
|
|
6
10
|
|
7
11
|
## Features:
|
@@ -18,7 +22,7 @@
|
|
18
22
|
|
19
23
|
### Sophisticated Expectations/Matchers System
|
20
24
|
|
21
|
-
### < 300 LOC
|
25
|
+
### < 300 LOC (sans doc)
|
22
26
|
|
23
27
|
|
24
28
|
|
@@ -71,8 +75,8 @@ Run it with the ruby command:
|
|
71
75
|
|
72
76
|
Use the default rails test directory/structure, testhelpers, assertions and infrastructure.
|
73
77
|
|
74
|
-
1.
|
75
|
-
2.
|
78
|
+
1. require 'speccify' in `test_helper.rb`
|
79
|
+
2. write a spec.
|
76
80
|
3. There is no step three ...
|
77
81
|
|
78
82
|
Tell Speccify what kind of test he is, by passing one of the following options to the describe method:
|
data/lib/speccify.rb
CHANGED
@@ -1,34 +1,40 @@
|
|
1
1
|
require 'test/unit'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
instance_eval { @_result.instance_eval { @run_count ||= 0; @run_count -= 1} if defined?(@_result)}
|
8
|
-
end
|
9
|
-
end # Test::Unit::TestCase
|
2
|
+
# No test/unit default_test whining.
|
3
|
+
Test::Unit::TestCase.class_eval do
|
4
|
+
def default_test # :nodoc:
|
5
|
+
instance_eval { @_result.instance_eval { @run_count ||= 0; @run_count -= 1} if defined?(@_result)}
|
6
|
+
end
|
10
7
|
end
|
11
8
|
|
12
9
|
module Speccify
|
10
|
+
# These method extend the ExampleGroup as class methods.
|
11
|
+
# The methods defined in this module are available
|
12
|
+
# inside the example groups ('describe' blocks).
|
13
13
|
module ExampleGroupClassMethods
|
14
14
|
attr_accessor :desc, :setup_chained, :teardown_chained
|
15
15
|
@desc ||= ""
|
16
16
|
|
17
|
-
def setup_chained
|
17
|
+
def setup_chained # :nodoc:
|
18
18
|
@setup_chained || lambda {}
|
19
19
|
end
|
20
20
|
|
21
|
-
def teardown_chained
|
21
|
+
def teardown_chained # :nodoc:
|
22
22
|
@teardown_chained || lambda {}
|
23
23
|
end
|
24
24
|
|
25
|
+
# ==Run before each Test.
|
26
|
+
# The code in the block attached to this method will be run before each
|
27
|
+
# example in all subsequent, eventually nested example groups.
|
25
28
|
def before(type = :each, &block)
|
26
29
|
raise "unsupported before type: #{type}" unless type == :each
|
27
30
|
passed_through_setup = self.setup_chained
|
28
31
|
self.setup_chained = lambda { instance_eval(&passed_through_setup);instance_eval(&block) }
|
29
32
|
define_method :setup, &self.setup_chained
|
30
33
|
end
|
31
|
-
|
34
|
+
|
35
|
+
# ==Run after each Test.
|
36
|
+
# The code in the block attached to this method will be run after each
|
37
|
+
# example in all subsequent, eventually nested example groups.
|
32
38
|
def after(type = :each, &block)
|
33
39
|
raise "unsupported after type: #{type}" unless type == :each
|
34
40
|
passed_through_teardown = self.teardown_chained
|
@@ -36,71 +42,94 @@ module Speccify
|
|
36
42
|
define_method :teardown, &self.teardown_chained
|
37
43
|
end
|
38
44
|
|
45
|
+
# ==Define an Example Group.
|
46
|
+
# Alike TestCase.
|
39
47
|
def describe desc, &block
|
40
|
-
|
48
|
+
modules = self.ancestors
|
49
|
+
cls = Class.new(self.superclass) do |klass|
|
50
|
+
modules.each {|mod| include mod if (mod.class.to_s == "Module" && !(self < mod))}
|
51
|
+
end
|
41
52
|
Object.const_set self.name + desc.to_s.split(/\W+/).map { |s| s.capitalize }.join, cls
|
42
53
|
cls.setup_chained = self.setup_chained
|
43
54
|
cls.teardown_chained = self.teardown_chained
|
44
55
|
cls.desc = self.desc + " " + desc
|
45
|
-
cls.tests($1.constantize) if defined?(Rails) && self.name =~ /^(.*Controller)Test/
|
56
|
+
cls.tests($1.constantize) if defined?(Rails) && self.name =~ /^(.*(Controller|Helper|Mailer))Test/
|
46
57
|
cls.class_eval(&block)
|
47
58
|
end
|
48
59
|
|
60
|
+
# ==Define a test.
|
61
|
+
# Alike 'def test_whatever ..' test definition.
|
49
62
|
def it desc, &block
|
50
63
|
self.before {}
|
64
|
+
desc = Speccify::Functions.make_constantizeable(desc)
|
51
65
|
define_method "test_#{desc.gsub(/\W+/, '_').downcase}", &lambda {$current_spec = self; instance_eval(&block)} if block_given?
|
52
66
|
self.after {}
|
53
67
|
end
|
54
68
|
end # ExampleGroupClassMethods
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
Speccify::Functions::build_matcher(name, args) do |given, matcher, args|
|
60
|
-
given.send(($1 + "?").to_sym)
|
61
|
-
end
|
62
|
-
else
|
63
|
-
raise NoMethodError.new(name.to_s)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end # ExampleGroupMethods
|
67
|
-
|
69
|
+
|
70
|
+
# == Temporary
|
71
|
+
# A temporary module that holds functionality
|
72
|
+
# that awaits to be refactored to the right place.
|
68
73
|
module Functions
|
69
|
-
def self.determine_class_name(name)
|
74
|
+
def self.determine_class_name(name) #:nodoc:
|
70
75
|
name.to_s.split(/\W+/).map { |s| s[0..0].upcase + s[1..-1] }.join
|
71
76
|
end
|
72
77
|
|
78
|
+
# A matcher in speccify is a builder method that
|
79
|
+
# returns the right matcher object.
|
80
|
+
# This build_matcher method is the internal interface to the
|
81
|
+
# matcher system.
|
82
|
+
# (You should use the def_matcher() method to define matchers.)
|
83
|
+
#
|
84
|
+
# Example:
|
85
|
+
# # The obj.should be_true matcher.
|
86
|
+
# def be_true
|
87
|
+
# Speccify::Functions.build_matcher(:be_true, []) do |given, matcher, args|
|
88
|
+
# given
|
89
|
+
# end
|
90
|
+
# end
|
73
91
|
def self.build_matcher(matcher_name, args, &block)
|
74
92
|
match_block = lambda do |actual, matcher|
|
75
93
|
block.call(actual, matcher, args)
|
76
94
|
end
|
77
95
|
body = lambda do |klass|
|
78
96
|
@matcher_name = matcher_name.to_s
|
79
|
-
def self.matcher_name
|
97
|
+
def self.matcher_name #:nodoc:
|
80
98
|
@matcher_name
|
81
99
|
end
|
82
100
|
|
83
101
|
attr_accessor :positive_msg, :negative_msg, :msgs, :loc
|
84
|
-
def initialize match_block
|
102
|
+
def initialize match_block #:nodoc:
|
85
103
|
@match_block = match_block
|
86
104
|
end
|
87
105
|
|
88
|
-
def method_missing id, *args, &block
|
106
|
+
def method_missing id, *args, &block # :nodoc:
|
89
107
|
require 'ostruct'
|
90
108
|
(self.msgs ||= []) << OpenStruct.new( "name" => id, "args" => args, "block" => block )
|
91
109
|
self
|
92
110
|
end
|
93
111
|
|
94
|
-
def matches? given
|
95
|
-
@positive_msg ||= Speccify::Functions::message(
|
96
|
-
|
112
|
+
def matches? given #:nodoc:
|
113
|
+
@positive_msg ||= Speccify::Functions::message(
|
114
|
+
"#{given} should #{self.class.matcher_name}", "no match", self.class.matcher_name , self.loc || "")
|
115
|
+
@negative_msg ||= Speccify::Functions::message(
|
116
|
+
"#{given} should not #{self.class.matcher_name}", "match", self.class.matcher_name , self.loc || "")
|
97
117
|
@match_block.call(given, self)
|
98
118
|
end
|
99
119
|
end
|
100
120
|
Class.new(&body).new(match_block)
|
101
121
|
end
|
102
122
|
|
103
|
-
def self.
|
123
|
+
def self.make_constantizeable(string)
|
124
|
+
return string unless string.class.to_s == "String"
|
125
|
+
string = string.gsub(/[^\w\s]/,"").gsub(/^[\d\s]*/,"")
|
126
|
+
raise ArgumentError.new(
|
127
|
+
"Invalid argument. Must not be empty after removing '\W'-class chars."
|
128
|
+
) if string.gsub(/\s/,"").empty?
|
129
|
+
string
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.message(expected, actual, op, location) # :nodoc:
|
104
133
|
"""
|
105
134
|
Expected: #{expected.to_s}
|
106
135
|
got: #{actual.to_s}
|
@@ -111,7 +140,7 @@ module Speccify
|
|
111
140
|
end # Functions
|
112
141
|
|
113
142
|
class OperatorMatcherProxy
|
114
|
-
def self.create given, loc, type = true
|
143
|
+
def self.create given, loc, type = true # :nodoc:
|
115
144
|
body = lambda do |klass|
|
116
145
|
define_method(:initialize) do |given|
|
117
146
|
@given = given
|
@@ -122,7 +151,7 @@ module Speccify
|
|
122
151
|
print_given = (@given == nil) ? "nil" : @given
|
123
152
|
print_actual = (type ? "" : "not ") + (actual.nil? ? "nil" : actual.to_s)
|
124
153
|
msg = Speccify::Functions::message(print_actual, print_given, operator, loc)
|
125
|
-
$current_spec.assert(type == @given.send(operator,actual), msg)
|
154
|
+
$current_spec.assert(type == (@given.send(operator,actual) ? true : false), msg)
|
126
155
|
end
|
127
156
|
end
|
128
157
|
end
|
@@ -131,7 +160,14 @@ module Speccify
|
|
131
160
|
end
|
132
161
|
end # OperatorMatcherProxy
|
133
162
|
|
134
|
-
Object
|
163
|
+
class ::Object
|
164
|
+
# == obj.should matcher
|
165
|
+
# Set an expectation to this object. Can be used in conjunction with
|
166
|
+
# an operator (except !=, !~) or a matcher.
|
167
|
+
# ===Examples:
|
168
|
+
# * 1.should == 1
|
169
|
+
# * "asd".should =~ /a/
|
170
|
+
# * nil.should be_nil
|
135
171
|
def should matcher = nil
|
136
172
|
if matcher
|
137
173
|
matcher.loc = caller[0]
|
@@ -140,7 +176,13 @@ module Speccify
|
|
140
176
|
OperatorMatcherProxy.create(self,caller[0])
|
141
177
|
end
|
142
178
|
end
|
143
|
-
|
179
|
+
# == obj.should_not matcher
|
180
|
+
# Set a negative expectation to this object. Can be used in conjunction with
|
181
|
+
# an operator (except !=, !~) or a matcher.
|
182
|
+
# ===Examples:
|
183
|
+
# * 1.should_not == 2
|
184
|
+
# * "asd".should_not =~ /b/
|
185
|
+
# * 1.should_not be_nil
|
144
186
|
def should_not matcher = nil
|
145
187
|
if matcher
|
146
188
|
matcher.loc = caller[0]
|
@@ -152,6 +194,43 @@ module Speccify
|
|
152
194
|
end # Object
|
153
195
|
|
154
196
|
module Extension
|
197
|
+
# == Custom matchers for (almost) FREE
|
198
|
+
#
|
199
|
+
# === A simple example first:
|
200
|
+
#
|
201
|
+
# def_matcher :be_nil do |given, matcher, args|
|
202
|
+
# given.nil?
|
203
|
+
# end
|
204
|
+
# nil.should be_nil
|
205
|
+
# *provide def_matcher() the name of your matcher and attach a block
|
206
|
+
# that defines it's behavior.
|
207
|
+
# *The return value of the block is a boolean that
|
208
|
+
# actually will be expected (should) or not expected (`should_not`).
|
209
|
+
# ==Three arguments are available inside the matcher block:
|
210
|
+
# ===given
|
211
|
+
# The object that has received the should or `should_not`.
|
212
|
+
# === matcher
|
213
|
+
# The matcher object. You can set the failure messages as attributes on this object:
|
214
|
+
# matcher.positive_msg = "You can see me if I am applied to should and I return a false value"
|
215
|
+
# matcher.negative_msg = "You can see me if I am applied to should_not and I return a true value"
|
216
|
+
# It holds a list of all methods that have been called on the matcher (for chaining):
|
217
|
+
#
|
218
|
+
# obj.should matcher_name.some_method(4,5,6) {"and a block"}.second
|
219
|
+
# def_matcher :matcher_name do |given, matcher, args|
|
220
|
+
# # this is an ostruct that holds all information about the first method 'some_method'
|
221
|
+
# matcher.msgs[0]
|
222
|
+
# # this is an ostruct that holds all information about the second method 'second'
|
223
|
+
# matcher.msgs[1]
|
224
|
+
# # this is the name of the first method:
|
225
|
+
# matcher.msgs[0].name #=> :some_method
|
226
|
+
# # this is a list of arguments that have been passed to the first method:
|
227
|
+
# matcher.msgs[0].args #=> [4,5,6]
|
228
|
+
# # this is the block that was attached:
|
229
|
+
# matcher.msgs[0].block #=> proc {"and a block"}
|
230
|
+
# end
|
231
|
+
# === args
|
232
|
+
# A list of all arguments that have been applied to the matcher. Like the 6 in:
|
233
|
+
# (3*3).should_not be(6)
|
155
234
|
def def_matcher(matcher_name, &block)
|
156
235
|
self.class.send :define_method, matcher_name do |*args|
|
157
236
|
Speccify::Functions::build_matcher(matcher_name, args, &block)
|
@@ -162,9 +241,10 @@ module Speccify
|
|
162
241
|
module ::Kernel
|
163
242
|
def describe *args, &block
|
164
243
|
super_class = (Hash === args.last && (args.last[:type] || args.last[:testcase])) || Test::Unit::TestCase
|
165
|
-
super_class.class_eval {extend ExampleGroupClassMethods
|
244
|
+
super_class.class_eval {extend ExampleGroupClassMethods}
|
166
245
|
cls = Class.new(super_class)
|
167
246
|
cnst, desc = args
|
247
|
+
cnst = Speccify::Functions.make_constantizeable(cnst)
|
168
248
|
Object.const_set Speccify::Functions::determine_class_name(cnst.to_s + "Test"), cls
|
169
249
|
cls.desc = String === desc ? desc : cnst.to_s
|
170
250
|
cls.class_eval(&block)
|
@@ -174,11 +254,30 @@ module Speccify
|
|
174
254
|
end # Speccify
|
175
255
|
include Speccify::Extension
|
176
256
|
|
177
|
-
#
|
257
|
+
# same as should == ...
|
178
258
|
def_matcher :be do |given, matcher, args|
|
179
259
|
given == args[0]
|
180
260
|
end
|
181
|
-
|
261
|
+
# expect obj.eql?(arg)
|
262
|
+
def_matcher :eql do |given, matcher, args|
|
263
|
+
given.eql?(args[0])
|
264
|
+
end
|
265
|
+
# expect an exception
|
266
|
+
def_matcher :raise_error do |given, matcher, args|
|
267
|
+
raised = false
|
268
|
+
begin
|
269
|
+
given.call
|
270
|
+
rescue Exception => e
|
271
|
+
raised = true
|
272
|
+
end
|
273
|
+
# todo clean this up
|
274
|
+
raised && (args[0].kind_of?(Regexp) ? e.message =~ args[0] : (args[0].nil? ? true : args[0] == e.message))
|
275
|
+
end
|
276
|
+
# == lambda {do_something}.should change {something}
|
277
|
+
# * lambda {var += 1}.should change {var}.by(1)
|
278
|
+
# * lambda {var += 2}.should change {var}.by_at_least(1)
|
279
|
+
# * lambda {var += 1}.should change {var}.by_at_most(1)
|
280
|
+
# * lambda {var += 2}.should change {var}.from(1).to(3) if var = 1
|
182
281
|
def change(&block)
|
183
282
|
Speccify::Functions::build_matcher(:change, []) do |given, matcher, args|
|
184
283
|
before = block.call
|
@@ -198,4 +297,19 @@ def change(&block)
|
|
198
297
|
matcher.negative_msg = "given block did alter the block attached to change, #{matcher.loc}"
|
199
298
|
comparison
|
200
299
|
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# ==be_*something*
|
303
|
+
# ===This method_missing acts as a matcher builder method.
|
304
|
+
# If a call to be_xyz() reaches this method_missing (say: obj.should be_xyz),
|
305
|
+
# a matcher with the name xyz will be built, whose defining property
|
306
|
+
# is that it returns the value of obj.xyz? for matches?.
|
307
|
+
def method_missing(name, *args, &block)
|
308
|
+
if (name.to_s =~ /^be_(.+)/)
|
309
|
+
Speccify::Functions::build_matcher(name, args) do |given, matcher, args|
|
310
|
+
given.send(($1 + "?").to_sym)
|
311
|
+
end
|
312
|
+
else
|
313
|
+
raise NoMethodError.new(name.to_s)
|
314
|
+
end
|
201
315
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: speccify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthias Hennemeyer
|
@@ -9,11 +9,11 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-02-
|
12
|
+
date: 2009-02-04 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
|
-
description: Speccify is a lightweight
|
16
|
+
description: Speccify is a lightweight bdd framework that is built on top of test/unit.
|
17
17
|
email: mhennemeyer@gmail.com
|
18
18
|
executables: []
|
19
19
|
|
@@ -49,6 +49,6 @@ rubyforge_project:
|
|
49
49
|
rubygems_version: 1.3.1
|
50
50
|
signing_key:
|
51
51
|
specification_version: 2
|
52
|
-
summary: Speccify
|
52
|
+
summary: Speccify, the lightweight option.
|
53
53
|
test_files: []
|
54
54
|
|