speccify 0.3.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.
Files changed (3) hide show
  1. data/README.markdown +269 -0
  2. data/lib/speccify.rb +201 -0
  3. metadata +54 -0
data/README.markdown ADDED
@@ -0,0 +1,269 @@
1
+ # Speccify
2
+
3
+ ## A lightweight alternative to RSpec.
4
+
5
+
6
+
7
+ ## Features:
8
+
9
+ ### Nested Contexts
10
+
11
+ ### Custom matchers are no-brainers with def_matcher
12
+
13
+ ### Blazing Fast
14
+
15
+ ### 100% Ruby 1.9 and 1.8 compatible
16
+
17
+ ### Rails out of the box!
18
+
19
+ ### Sophisticated Expectations/Matchers System
20
+
21
+ ### < 300 LOC
22
+
23
+
24
+
25
+ ## Install Speccify:
26
+
27
+ > sudo gem install speccify
28
+
29
+ ## Using Speccify
30
+
31
+ Create a testfile:
32
+
33
+ # test_object.rb
34
+
35
+ require "rubygems"
36
+ require "speccify"
37
+
38
+ describe Object do
39
+ before do
40
+ @obj = Object.new
41
+ end
42
+
43
+ it "is not nil" do
44
+ @obj.should_not be_nil
45
+ end
46
+
47
+ it "can be frozen" do
48
+ @obj.freeze
49
+ @obj.should be_frozen
50
+ end
51
+ end
52
+
53
+ Run it with the ruby command:
54
+
55
+ > $ ruby test_object.rb
56
+ >Loaded suite -
57
+ >Started
58
+ >..
59
+ >Finished in 0.001820 seconds.
60
+ >
61
+ >2 tests, 2 assertions, 0 failures, 0 errors
62
+
63
+
64
+ ## Using Speccify with autotest
65
+
66
+ 1. Name your testfiles test_whatever.rb
67
+ 2. Start autotest
68
+ 3. There is no step three ...
69
+
70
+ ## Using Speccify with Rails
71
+
72
+ Use the default rails test directory/structure, testhelpers, assertions and infrastructure.
73
+
74
+ 1. sudo use_minitest yes
75
+ 2. require 'speccify' in test_helper.rb
76
+ 3. There is no step three ...
77
+
78
+ Tell Speccify what kind of test he is, by passing one of the following options to the describe method:
79
+
80
+ * :type => ActiveSupport::TestCase
81
+ * :type => ActionController::TestCase
82
+ * :type => ActionMailer::TestCase
83
+ * :type => ActionView::TestCase
84
+
85
+ ### Example, Functional Test:
86
+
87
+ describe HorstsController, :type => ActionController::TestCase do
88
+ it "should get index" do
89
+ get :index
90
+ @response.should be_success
91
+ assigns(:horsts).should_not be_nil
92
+ end
93
+ describe "Whatever" do
94
+ # I'm still a ActionController::TestCase
95
+ it "should get index" do
96
+ get :index
97
+ @response.should be_success
98
+ assigns(:horsts).should_not be_nil
99
+ end
100
+ end
101
+ end
102
+
103
+ I'm not sure if I should wrap all the rails assertions in matchers.
104
+ Using the default rails assertions works fine.
105
+
106
+
107
+ ## Built in matchers
108
+
109
+ * be_something, for any arbitrary something:
110
+
111
+ @obj.should be_something
112
+ # passes if @obj.something? is true
113
+
114
+
115
+ * have(n).somethings, for any arbitrary something:
116
+
117
+ @obj.should have(3).somethings
118
+ # passes if @obj.somethings.length == 3
119
+
120
+ * change {something}
121
+
122
+ lambda {@var+=1}.should change {@var}
123
+ # passes
124
+ lambda { }.should change {@var}
125
+ # fails
126
+ @var = 1
127
+ lambda {@var+=1}.should change {@var}.from(1).to(2)
128
+ # passes
129
+
130
+ * more
131
+
132
+ ## DEF_MATCHER
133
+
134
+ ### A simple example first:
135
+
136
+ def_matcher :be_nil do |given, matcher, args|
137
+ given.nil?
138
+ end
139
+ nil.should be_nil
140
+
141
+
142
+ The `def_matcher` method is really simple to use.
143
+ You just provide it the name of your matcher and attach a block that defines it's behavior.
144
+ The return value of the block is a boolean that
145
+ actually will be expected (should) or not expected (`should_not`).
146
+
147
+ There are three arguments available inside the matcher block:
148
+
149
+ ### given
150
+
151
+ This is the object that has received the should or `should_not`.
152
+
153
+ ### matcher
154
+
155
+ This is the matcher object. You can set the failure messages as attributes on this object:
156
+
157
+ def_matcher :matcher_name do |given, matcher, args|
158
+ matcher.positive_msg = "You can see me if I am applied to should and I return a false value"
159
+ matcher.negative_msg = "You can see me if I am applied to should_not and I return a true value"
160
+ end
161
+
162
+ It holds a list of all methods that have been called on the matcher (for chaining):
163
+
164
+ obj.should matcher_name.some_method(4,5,6) {"and a block"}.second
165
+ def_matcher :matcher_name do |given, matcher, args|
166
+ # this is an ostruct that holds all information about the first method 'some_method'
167
+ matcher.msgs[0]
168
+ # this is an ostruct that holds all information about the second method 'second'
169
+ matcher.msgs[1]
170
+ # this is the name of the first method:
171
+ matcher.msgs[0].name #=> :some_method
172
+ # this is a list of arguments that have been passed to the first method:
173
+ matcher.msgs[0].args #=> [4,5,6]
174
+ # this is the block that was attached:
175
+ matcher.msgs[0].block #=> proc {"and a block"}
176
+ end
177
+
178
+ If there is a failure, it knows where:
179
+
180
+ def_matcher :matcher_name do |given, matcher, args|
181
+ matcher.loc #=> "./some/where.rb:55 ... "
182
+ end
183
+
184
+ ### args
185
+ This is a list of all arguments that have been applied to the matcher. Like the 6 in:
186
+
187
+ (3*3).should_not be(6)
188
+
189
+ ## More def_matcher examples
190
+
191
+ ### A little more complex:
192
+
193
+ def_matcher :be_in_range do |given, matcher, args|
194
+ range = args[1] ? (args[0]..args[1]) : args[0]
195
+ matcher.positive_msg = "expected #{given} to be in range (#{range})"
196
+ matcher.negative_msg = "expected #{given} not to be in range (#{range})"
197
+ range.include?(given)
198
+ end
199
+ 2.should be_in_range(1,3)
200
+ "m".should be_in_range("a".."z")
201
+
202
+ ### Matchers may receive messages:
203
+
204
+ def_matcher :have do |given, matcher, args|
205
+ number = args[0]
206
+ actual = given.send(matcher.msgs[0].name).length
207
+ matcher.positive_msg = "Expected #{given} to have #{actual}, but found #{number} "
208
+ actual == number
209
+ end
210
+ class Thing
211
+ def widgets
212
+ @widgets ||= []
213
+ end
214
+ end
215
+ @thing.should have(3).widgets
216
+
217
+
218
+ ## Speccify vs. RSpec
219
+
220
+ ### Speccify provides the parts of RSpec that actually matter (to me):
221
+
222
+ * **The familiar describe/it syntax**
223
+ * **nested contexts**
224
+ * **should/should_not**
225
+ * **matchers and custom matchers**
226
+
227
+ ### Things that actually doesn't matter (to me):
228
+
229
+ * **/spec directory instead of /test**
230
+ * **`whatever_spec.rb` instead of `test_whatever.rb`**
231
+ * **custom formatters**
232
+ * **custom runners**
233
+ * **html output**
234
+ * **special commandline tools**
235
+ * **shared example groups**
236
+ * **pending examples**
237
+ * **`before_all, after_all, before_suite, after_suite`**
238
+
239
+
240
+ ## Contribution
241
+
242
+ * Idea?, Feature Request?, Bug? -> [Lighthouse](http://300.lighthouseapp.com/projects/24443/home)
243
+ * source -> [GitHub](http://github.com/mhennemeyer/speccify)
244
+ * talk? -> [GoogleGroup](http://groups.google.de/group/speccify)
245
+
246
+ ## License
247
+
248
+ (The MIT License)
249
+
250
+ Copyright (c) Matthias Hennemeyer
251
+
252
+ Permission is hereby granted, free of charge, to any person obtaining
253
+ a copy of this software and associated documentation files (the
254
+ 'Software'), to deal in the Software without restriction, including
255
+ without limitation the rights to use, copy, modify, merge, publish,
256
+ distribute, sublicense, and/or sell copies of the Software, and to
257
+ permit persons to whom the Software is furnished to do so, subject to
258
+ the following conditions:
259
+
260
+ The above copyright notice and this permission notice shall be
261
+ included in all copies or substantial portions of the Software.
262
+
263
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
264
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
265
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
266
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
267
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
268
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
269
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/lib/speccify.rb ADDED
@@ -0,0 +1,201 @@
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
10
+ end
11
+
12
+ module Speccify
13
+ module ExampleGroupClassMethods
14
+ attr_accessor :desc, :setup_chained, :teardown_chained
15
+ @desc ||= ""
16
+
17
+ def setup_chained
18
+ @setup_chained || lambda {}
19
+ end
20
+
21
+ def teardown_chained
22
+ @teardown_chained || lambda {}
23
+ end
24
+
25
+ def before(type = :each, &block)
26
+ raise "unsupported before type: #{type}" unless type == :each
27
+ passed_through_setup = self.setup_chained
28
+ self.setup_chained = lambda { instance_eval(&passed_through_setup);instance_eval(&block) }
29
+ define_method :setup, &self.setup_chained
30
+ end
31
+
32
+ def after(type = :each, &block)
33
+ raise "unsupported after type: #{type}" unless type == :each
34
+ passed_through_teardown = self.teardown_chained
35
+ self.teardown_chained = lambda {instance_eval(&block);instance_eval(&passed_through_teardown) }
36
+ define_method :teardown, &self.teardown_chained
37
+ end
38
+
39
+ def describe desc, &block
40
+ cls = Class.new(self.superclass)
41
+ Object.const_set self.name + desc.to_s.split(/\W+/).map { |s| s.capitalize }.join, cls
42
+ cls.setup_chained = self.setup_chained
43
+ cls.teardown_chained = self.teardown_chained
44
+ cls.desc = self.desc + " " + desc
45
+ cls.tests($1.constantize) if defined?(Rails) && self.name =~ /^(.*Controller)Test/
46
+ cls.class_eval(&block)
47
+ end
48
+
49
+ def it desc, &block
50
+ self.before {}
51
+ define_method "test_#{desc.gsub(/\W+/, '_').downcase}", &lambda {$current_spec = self; instance_eval(&block)} if block_given?
52
+ self.after {}
53
+ end
54
+ 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
+
68
+ module Functions
69
+ def self.determine_class_name(name)
70
+ name.to_s.split(/\W+/).map { |s| s[0..0].upcase + s[1..-1] }.join
71
+ end
72
+
73
+ def self.build_matcher(matcher_name, args, &block)
74
+ match_block = lambda do |actual, matcher|
75
+ block.call(actual, matcher, args)
76
+ end
77
+ body = lambda do |klass|
78
+ @matcher_name = matcher_name.to_s
79
+ def self.matcher_name
80
+ @matcher_name
81
+ end
82
+
83
+ attr_accessor :positive_msg, :negative_msg, :msgs, :loc
84
+ def initialize match_block
85
+ @match_block = match_block
86
+ end
87
+
88
+ def method_missing id, *args, &block
89
+ require 'ostruct'
90
+ (self.msgs ||= []) << OpenStruct.new( "name" => id, "args" => args, "block" => block )
91
+ self
92
+ end
93
+
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 || "")
97
+ @match_block.call(given, self)
98
+ end
99
+ end
100
+ Class.new(&body).new(match_block)
101
+ end
102
+
103
+ def self.message(expected, actual, op, location)
104
+ """
105
+ Expected: #{expected.to_s}
106
+ got: #{actual.to_s}
107
+ comparison with: #{op.to_s}
108
+ location: (#{location})
109
+ """
110
+ end
111
+ end # Functions
112
+
113
+ class OperatorMatcherProxy
114
+ def self.create given, loc, type = true
115
+ body = lambda do |klass|
116
+ define_method(:initialize) do |given|
117
+ @given = given
118
+ end
119
+
120
+ ['==', '===', '=~', '>', '>=', '<', '<='].each do |operator|
121
+ define_method(operator) do |actual|
122
+ print_given = (@given == nil) ? "nil" : @given
123
+ print_actual = (type ? "" : "not ") + (actual.nil? ? "nil" : actual.to_s)
124
+ msg = Speccify::Functions::message(print_actual, print_given, operator, loc)
125
+ $current_spec.assert(type == @given.send(operator,actual), msg)
126
+ end
127
+ end
128
+ end
129
+
130
+ return Class.new(&body).new(given)
131
+ end
132
+ end # OperatorMatcherProxy
133
+
134
+ Object.class_eval do
135
+ def should matcher = nil
136
+ if matcher
137
+ matcher.loc = caller[0]
138
+ $current_spec.assert(matcher.matches?(self), matcher.positive_msg)
139
+ else
140
+ OperatorMatcherProxy.create(self,caller[0])
141
+ end
142
+ end
143
+
144
+ def should_not matcher = nil
145
+ if matcher
146
+ matcher.loc = caller[0]
147
+ $current_spec.assert(!matcher.matches?(self), matcher.negative_msg)
148
+ else
149
+ OperatorMatcherProxy.create(self,caller[0], false)
150
+ end
151
+ end
152
+ end # Object
153
+
154
+ module Extension
155
+ def def_matcher(matcher_name, &block)
156
+ self.class.send :define_method, matcher_name do |*args|
157
+ Speccify::Functions::build_matcher(matcher_name, args, &block)
158
+ end
159
+ end
160
+ end # Extension
161
+
162
+ module ::Kernel
163
+ def describe *args, &block
164
+ super_class = (Hash === args.last && (args.last[:type] || args.last[:testcase])) || Test::Unit::TestCase
165
+ super_class.class_eval {extend ExampleGroupClassMethods; include ExampleGroupMethods}
166
+ cls = Class.new(super_class)
167
+ cnst, desc = args
168
+ Object.const_set Speccify::Functions::determine_class_name(cnst.to_s + "Test"), cls
169
+ cls.desc = String === desc ? desc : cnst.to_s
170
+ cls.class_eval(&block)
171
+ end
172
+ private :describe
173
+ end # Kernel
174
+ end # Speccify
175
+ include Speccify::Extension
176
+
177
+ # A few matchers:
178
+ def_matcher :be do |given, matcher, args|
179
+ given == args[0]
180
+ end
181
+
182
+ def change(&block)
183
+ Speccify::Functions::build_matcher(:change, []) do |given, matcher, args|
184
+ before = block.call
185
+ given.call
186
+ after = block.call
187
+ comparison = after != before
188
+ if list = matcher.msgs
189
+ comparison = case list[0].name
190
+ # todo provide meaningful messages
191
+ when :by then (after == before + list[0].args[0] || after == before - list[0].args[0])
192
+ when :by_at_least then (after >= before + list[0].args[0] || after <= before - list[0].args[0])
193
+ when :by_at_most then (after <= before + list[0].args[0] && after >= before - list[0].args[0])
194
+ when :from then (before == list[0].args[0]) && (after == list[1].args[0])
195
+ end
196
+ end
197
+ matcher.positive_msg = "given block didn't alter the block attached to change, #{matcher.loc}"
198
+ matcher.negative_msg = "given block did alter the block attached to change, #{matcher.loc}"
199
+ comparison
200
+ end
201
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: speccify
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthias Hennemeyer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-01 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Speccify is a lightweight alternative to RSpec.
17
+ email: mhennemeyer@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - README.markdown
26
+ - lib/speccify.rb
27
+ has_rdoc: false
28
+ homepage: http://github.com/mhennemeyer/speccify
29
+ post_install_message:
30
+ rdoc_options: []
31
+
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: "0"
39
+ version:
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ version:
46
+ requirements: []
47
+
48
+ rubyforge_project:
49
+ rubygems_version: 1.3.1
50
+ signing_key:
51
+ specification_version: 2
52
+ summary: Speccify is a lightweight alternative to RSpec.
53
+ test_files: []
54
+