speccify 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.markdown +8 -4
  2. data/lib/speccify.rb +155 -41
  3. metadata +4 -4
@@ -1,7 +1,11 @@
1
1
  # Speccify
2
2
 
3
- ## A lightweight alternative to RSpec.
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. sudo use_minitest yes
75
- 2. require 'speccify' in test_helper.rb
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:
@@ -1,34 +1,40 @@
1
1
  require 'test/unit'
2
-
3
-
4
- unless defined?(MiniTest)
5
- Test::Unit::TestCase.class_eval do
6
- def default_test
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
- cls = Class.new(self.superclass)
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
- module ExampleGroupMethods
57
- def method_missing(name, *args, &block)
58
- if (name.to_s =~ /^be_(.+)/)
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("#{given} should #{self.class.matcher_name}", "no match", self.class.matcher_name , self.loc || "")
96
- @negative_msg ||= Speccify::Functions::message("#{given} should not #{self.class.matcher_name}", "match", self.class.matcher_name , self.loc || "")
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.message(expected, actual, op, location)
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.class_eval do
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; include ExampleGroupMethods}
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
- # A few matchers:
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.0
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-01 00:00:00 +01:00
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 alternative to RSpec.
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 is a lightweight alternative to RSpec.
52
+ summary: Speccify, the lightweight option.
53
53
  test_files: []
54
54