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.
- data/README.markdown +269 -0
- data/lib/speccify.rb +201 -0
- 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
|
+
|