testme 0.4.6
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/.gitignore +7 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/.testme +5 -0
- data/Gemfile +6 -0
- data/LICENSE +13 -0
- data/README.md +341 -0
- data/bin/testme +62 -0
- data/lib/double.rb +23 -0
- data/lib/formatter.rb +143 -0
- data/lib/logic.rb +141 -0
- data/lib/symbol.rb +10 -0
- data/lib/testme.rb +87 -0
- data/spec/double_spec.rb +54 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/testme_spec.rb +159 -0
- data/test/app.test.rb +0 -0
- data/testme.gemspec +30 -0
- metadata +104 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.9.3
|
data/.testme
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2012 Daniel Shuey
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,341 @@
|
|
1
|
+
# Test Me
|
2
|
+
*A minimalistic testing framework*
|
3
|
+
|
4
|
+
test Me
|
5
|
+
given simple: true
|
6
|
+
is? :simple
|
7
|
+
|
8
|
+
## Detailed Example
|
9
|
+
|
10
|
+
test Pizza
|
11
|
+
given owner: 'Santoni', style: 'Pepperoni' # Set the context
|
12
|
+
is? :name, "Santoni's Pepperoni Pizza"
|
13
|
+
|
14
|
+
given name: 'Michel' # Reset the context
|
15
|
+
is? :name, "Santoni's Pepperoni Pizza"
|
16
|
+
|
17
|
+
also :hawaiian_style, style: 'Hawaiian' # Add on to previous context and save it
|
18
|
+
is? :name, "Michels's Hawaiian Pizza"
|
19
|
+
|
20
|
+
given :hawaiian_style # Reload the saved context
|
21
|
+
also { topic.owner = 'Santoni' } # Use a block instead
|
22
|
+
is? :name, "Santoni's Hawaiian Pizza"
|
23
|
+
|
24
|
+
> ### Output
|
25
|
+
|
26
|
+
> test Pizza
|
27
|
+
> given owner: 'Santoni', style: 'Pepperoni'
|
28
|
+
> is name, Santoni's Pepperoni Pizza? YES
|
29
|
+
>
|
30
|
+
> given name: 'Michel'
|
31
|
+
> is name, Santoni's Pepperoni Pizza? NO, it was Michel's Pizza!
|
32
|
+
>
|
33
|
+
> also :hawaiian_style: style: 'Hawaiian'
|
34
|
+
> is name, Michel's Hawaiian Pizza? YES
|
35
|
+
>
|
36
|
+
> given :hawaiian_style:
|
37
|
+
> also (block)
|
38
|
+
> is name, Santoni's Hawaiian Pizza? YES
|
39
|
+
|
40
|
+
## Setup
|
41
|
+
|
42
|
+
`gem install testme`
|
43
|
+
|
44
|
+
*Then in your project root directory*
|
45
|
+
|
46
|
+
`testme setup`
|
47
|
+
|
48
|
+
/.testme
|
49
|
+
/test/
|
50
|
+
/test/app.test.rb
|
51
|
+
|
52
|
+
*Default .testme boiler plate on setup*
|
53
|
+
*this might need to be reconfigured to cater to your project*
|
54
|
+
|
55
|
+
require "testme"
|
56
|
+
|
57
|
+
TESTME_DIR = '/test/'
|
58
|
+
TESTME_FORMAT = :console
|
59
|
+
TESTME_COLORS = :default
|
60
|
+
|
61
|
+
#### Example config with bundler
|
62
|
+
|
63
|
+
require "rubygems"
|
64
|
+
require "bundler"
|
65
|
+
require "bundler/setup"
|
66
|
+
Bundler.require :default, :test
|
67
|
+
|
68
|
+
TESTME_DIR = '/test/'
|
69
|
+
TESTME_FORMAT = :console
|
70
|
+
TESTME_COLORS = :default
|
71
|
+
|
72
|
+
## Config
|
73
|
+
|
74
|
+
In your project root will be a `.testme` file, this is your bootstrap that runs before your tests
|
75
|
+
|
76
|
+
The `/test/` folder will house all your test files, you can change this in your bootstrap
|
77
|
+
|
78
|
+
### Config options
|
79
|
+
|
80
|
+
TESTME_DIR = '/test/'
|
81
|
+
* default: '/test/'
|
82
|
+
|
83
|
+
TESTME_FORMAT = :console
|
84
|
+
* choose how results are displayed
|
85
|
+
* options: :none, :text, :console
|
86
|
+
* default: :console
|
87
|
+
|
88
|
+
TESTME_COLORS = :default
|
89
|
+
* color scheme for console output
|
90
|
+
* options: :default
|
91
|
+
* default: :default
|
92
|
+
|
93
|
+
***
|
94
|
+
|
95
|
+
## Run
|
96
|
+
|
97
|
+
#### Test all
|
98
|
+
default test folder: `/test/**/*`
|
99
|
+
|
100
|
+
$ testme
|
101
|
+
|
102
|
+
#### Test all in directory
|
103
|
+
|
104
|
+
$ testme test/features
|
105
|
+
|
106
|
+
#### Test file
|
107
|
+
|
108
|
+
$ testme test/account.test.rb
|
109
|
+
|
110
|
+
#### Inline testing
|
111
|
+
|
112
|
+
$ testme app/models
|
113
|
+
|
114
|
+
##### Inline testing examples
|
115
|
+
|
116
|
+
>
|
117
|
+
|
118
|
+
class Account
|
119
|
+
end
|
120
|
+
|
121
|
+
testme do
|
122
|
+
test Account
|
123
|
+
end
|
124
|
+
|
125
|
+
>
|
126
|
+
|
127
|
+
class Account
|
128
|
+
|
129
|
+
field :name
|
130
|
+
|
131
|
+
def what_is_my_name
|
132
|
+
return name
|
133
|
+
end
|
134
|
+
|
135
|
+
testme {
|
136
|
+
test Account
|
137
|
+
given name: 'Fred'
|
138
|
+
is? what_is_my_name: 'Fred'
|
139
|
+
}
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
>
|
144
|
+
|
145
|
+
class Account
|
146
|
+
extend TestMe; test Account
|
147
|
+
|
148
|
+
field :name
|
149
|
+
|
150
|
+
def what_is_my_name
|
151
|
+
return name
|
152
|
+
end
|
153
|
+
|
154
|
+
#what_is_my_name
|
155
|
+
given name: 'Fred'
|
156
|
+
is? what_is_my_name: 'Fred'
|
157
|
+
end
|
158
|
+
|
159
|
+
## Keywords
|
160
|
+
#### `test`
|
161
|
+
> Defines the topic of the test
|
162
|
+
|
163
|
+
test Account
|
164
|
+
|
165
|
+
>
|
166
|
+
|
167
|
+
test App::Account
|
168
|
+
|
169
|
+
> This automatically creates an instance which you access with `topic`
|
170
|
+
|
171
|
+
topic.name
|
172
|
+
|
173
|
+
> Both modules and classes can be used for the test topic
|
174
|
+
|
175
|
+
test AccountHelper
|
176
|
+
|
177
|
+
> (At the moment you can only test instance methods, class methods will also be testable in a later update)
|
178
|
+
|
179
|
+
***
|
180
|
+
|
181
|
+
#### `given`
|
182
|
+
> Set the context
|
183
|
+
|
184
|
+
given first_name: 'Deckard', surname: 'Cain'
|
185
|
+
|
186
|
+
$ topic.first_name
|
187
|
+
$ => "Deckard"
|
188
|
+
|
189
|
+
$ topic.surname
|
190
|
+
$ => "Cain"
|
191
|
+
|
192
|
+
> Clear any previous context
|
193
|
+
|
194
|
+
given first_name: 'Deckard'
|
195
|
+
given first_name: 'Flavie'
|
196
|
+
|
197
|
+
$ topic.first_name
|
198
|
+
$ => "Flavie"
|
199
|
+
|
200
|
+
> Create or Overwrite methods that can return anything on the topic (Stubbing)
|
201
|
+
|
202
|
+
given non_existent_method: "this never existed before"
|
203
|
+
|
204
|
+
$ topic.non_existent_method
|
205
|
+
$ => "this never existed before"
|
206
|
+
|
207
|
+
> Create a context using a block
|
208
|
+
|
209
|
+
given { topic.talk_to 'Flavie' }
|
210
|
+
|
211
|
+
> Code run inside a block will still automatically stub for you
|
212
|
+
|
213
|
+
given { topic.non_existent_method = "this never existed before" }
|
214
|
+
|
215
|
+
> Store a context
|
216
|
+
|
217
|
+
given :deckard_cains_name, name: Name.new(first: 'Deckard', last: 'Cain')
|
218
|
+
|
219
|
+
> Load a context
|
220
|
+
|
221
|
+
given :deckard_cains_name
|
222
|
+
|
223
|
+
> Create a stub chain
|
224
|
+
|
225
|
+
given { topic.name.first = 'Deckard' }
|
226
|
+
|
227
|
+
> Stub chains will also only override the method you stub, while keeping the remaining associations intact
|
228
|
+
|
229
|
+
given :deckard_cains_name { topic.name.first = 'Flavie' }
|
230
|
+
|
231
|
+
$ topic.name.last
|
232
|
+
$ => "Cain"
|
233
|
+
|
234
|
+
***
|
235
|
+
|
236
|
+
#### `also`
|
237
|
+
> Provides a context over the existing context
|
238
|
+
|
239
|
+
given first_name: 'Deckard'
|
240
|
+
also surname: 'Cain'
|
241
|
+
|
242
|
+
$ topic.first_name
|
243
|
+
$ => "Deckard"
|
244
|
+
|
245
|
+
$ topic.surname
|
246
|
+
$ => "Cain"
|
247
|
+
|
248
|
+
> Anything you can do in `given` you can also do in `also`
|
249
|
+
|
250
|
+
given name: Name.new(first: 'Deckard', last: 'Cain')
|
251
|
+
also :flavies_name { topic.name.first = 'Flavie' }
|
252
|
+
|
253
|
+
given name: Name.new(first: 'Deckard', last: 'Cain')
|
254
|
+
also :flavies_name
|
255
|
+
|
256
|
+
$ topic.name.first
|
257
|
+
$ => "Flavie"
|
258
|
+
|
259
|
+
***
|
260
|
+
|
261
|
+
#### `is?`
|
262
|
+
> Creates an assertion
|
263
|
+
|
264
|
+
is? :first_name, 'Deckard'
|
265
|
+
|
266
|
+
> If there are arguments, do like so
|
267
|
+
|
268
|
+
is? :sum[2,2], 4
|
269
|
+
|
270
|
+
> Or you can also use a block
|
271
|
+
|
272
|
+
is? {topic.sum(2, 2) == 4}
|
273
|
+
|
274
|
+
> If the expected value is `true` you don't have to state it
|
275
|
+
|
276
|
+
is? :simple?
|
277
|
+
|
278
|
+
***
|
279
|
+
|
280
|
+
#### `before`
|
281
|
+
> Creates a base context
|
282
|
+
|
283
|
+
> Remains until `test` is invoked again
|
284
|
+
|
285
|
+
before { topic.do_some_factory_stuff }
|
286
|
+
|
287
|
+
## "This is too easy! How can I make this more challenging?"
|
288
|
+
|
289
|
+
There is something about you that troubles me. Your manner is strange for a lonely code warrior.
|
290
|
+
|
291
|
+
## How long till version 1.0?
|
292
|
+
|
293
|
+
When these features are completed
|
294
|
+
|
295
|
+
- Test class methods (only instance methods can be tested at this time)
|
296
|
+
- Blocks need to parsed back into a string
|
297
|
+
- Test more complex method stubbing
|
298
|
+
- Console color schemes
|
299
|
+
- HTML reporting
|
300
|
+
|
301
|
+
## Design Decisions
|
302
|
+
|
303
|
+
#### Context Sensitive
|
304
|
+
|
305
|
+
I found that a lot of complexity with setting up tests is due to wrapping things in contexts. As tests are often executed sequentially from the top down anyway, this is often unnecessary.
|
306
|
+
|
307
|
+
This also eliminates teardown and tearup sequences, and can save a lot of time. Instead of executing our setup for every single test case, we only setup exactly what is needed once, and adjust as necessary between test cases. This works because we often readjust every state required to run the next test in order to maintain clarity. Meaning being able to hop in-between contexts is often unnecessary.
|
308
|
+
|
309
|
+
#### Self Documenting Tests
|
310
|
+
|
311
|
+
TestMe aims to reduce the use of code comments and test descriptions, and instead focusing more on self documenting code to describe our functionality.
|
312
|
+
|
313
|
+
Especially when describing contexts there is no need to re-describe them. Abstracting explicit tests and describing them with behaviour also makes tests much more difficult to maintain or delete.
|
314
|
+
|
315
|
+
#### DRY Contexts
|
316
|
+
|
317
|
+
In order to get around not having wrapped contexts we use re-usable contexts, that can be defined during the creation of the context itself. With other testing frameworks we have to define a method, however if you want to stay BDD this means you have to re-describe the context when you re-use it.
|
318
|
+
|
319
|
+
Cucumber solves this issue by allowing you to use a sentence structure to load a context. The only problem with this is you have to create steps for even the most specific contexts which are often un-reusable, and these contexts are specified away from the tests. This causes a lot of disorganisation and fragmentation without even the most diligent testers. It makes your test code bloat really fast, and makes it difficult to control. Even in professional teams, you will often see thousands of lines of dormant test code in a large project that nobody knew even existed.
|
320
|
+
|
321
|
+
TestMe draws on Cucumber's style while staying concise by allowing you to optionally define a description for the context, and just re-specify the description when re-using the context. Allowing the test to be self documenting. Keeping tests cases and logic together reduces fragmentation and makes tests easier to maintain.
|
322
|
+
|
323
|
+
## Credit
|
324
|
+
|
325
|
+
Daniel Shuey - daniel.shuey@gmail.com
|
326
|
+
|
327
|
+
## License
|
328
|
+
|
329
|
+
Copyright 2012 Daniel Shuey
|
330
|
+
|
331
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
332
|
+
you may not use this file except in compliance with the License.
|
333
|
+
You may obtain a copy of the License at
|
334
|
+
|
335
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
336
|
+
|
337
|
+
Unless required by applicable law or agreed to in writing, software
|
338
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
339
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
340
|
+
See the License for the specific language governing permissions and
|
341
|
+
limitations under the License.
|
data/bin/testme
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
if ARGV[0] == 'setup'
|
4
|
+
|
5
|
+
puts 'Creating bootstrap "/.testme"'
|
6
|
+
|
7
|
+
File.open('./.testme', 'w') {|f| f.write(
|
8
|
+
%q[require "testme"
|
9
|
+
|
10
|
+
TESTME_DIR = '/test/'
|
11
|
+
TESTME_FORMAT = :console
|
12
|
+
TESTME_COLORS = :default]
|
13
|
+
)}
|
14
|
+
|
15
|
+
puts 'Creating test folder "/test"'
|
16
|
+
|
17
|
+
Dir::mkdir('./test/') unless File.directory?("./test/") || File.exists?("./test/")
|
18
|
+
|
19
|
+
puts 'Creating empty test file "/app.test.rb"'
|
20
|
+
|
21
|
+
File.open('./test/app.test.rb', 'w') {|f| f.write(
|
22
|
+
""
|
23
|
+
)}
|
24
|
+
|
25
|
+
puts 'Setup complete'
|
26
|
+
|
27
|
+
else
|
28
|
+
|
29
|
+
TESTME_RUNNING = true
|
30
|
+
|
31
|
+
if File.exists?("#{Dir.pwd}/.testme")
|
32
|
+
load "#{Dir.pwd}/.testme"
|
33
|
+
else
|
34
|
+
require "testme"
|
35
|
+
end
|
36
|
+
|
37
|
+
if ARGV.empty?
|
38
|
+
testme do
|
39
|
+
Dir["#{Dir.pwd}/#{TestMe::TESTME_DIR}/**/*.rb"].each{|f| load f }
|
40
|
+
end
|
41
|
+
|
42
|
+
else
|
43
|
+
ARGV.each do |arg|
|
44
|
+
@file = "#{Dir.pwd}/#{arg}"
|
45
|
+
|
46
|
+
# Check if directory
|
47
|
+
if File.directory? @file
|
48
|
+
Dir["#{@file}/**/*.rb"].each{|f| load f }
|
49
|
+
elsif File.exists? @file
|
50
|
+
if @file.include? '.test.rb'
|
51
|
+
testme do
|
52
|
+
load @file
|
53
|
+
end
|
54
|
+
else
|
55
|
+
require @file
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
data/lib/double.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module TestMe
|
2
|
+
class Double
|
3
|
+
def initialize subject=nil
|
4
|
+
@subject = subject if subject
|
5
|
+
@dict = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(method, *args, &block)
|
9
|
+
if @subject
|
10
|
+
if @subject.respond_to?(method) && !@dict.key?(method)
|
11
|
+
return @subject.send(method, *args, &block)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
if method.to_s =~ /=$/
|
16
|
+
return @dict[method.to_s.match(/^(.*)=$/)[1].to_sym] = args.first
|
17
|
+
else
|
18
|
+
return @dict[method] ||= Double.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
data/lib/formatter.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
module TestMe
|
2
|
+
module Formatter
|
3
|
+
|
4
|
+
def self.create format
|
5
|
+
case format
|
6
|
+
when :none
|
7
|
+
return Formatter::None.new
|
8
|
+
when :text
|
9
|
+
return Formatter::Text.new
|
10
|
+
when :console
|
11
|
+
return Formatter::Console.new
|
12
|
+
when :html
|
13
|
+
return Formatter::HTML.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class None
|
18
|
+
def test topic; end
|
19
|
+
def given *args, █ end
|
20
|
+
def also *args, █ end
|
21
|
+
def is? *args; end
|
22
|
+
def compile; end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Html
|
26
|
+
def test topic; end
|
27
|
+
def given *args, █ end
|
28
|
+
def also *args, █ end
|
29
|
+
def is? *args; end
|
30
|
+
def compile; end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Text
|
34
|
+
def test topic
|
35
|
+
log "test " + topic.name
|
36
|
+
end
|
37
|
+
|
38
|
+
def given desc=nil, stubs=nil, &block
|
39
|
+
log "given " + context_to_string(desc, stubs, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def also desc=nil, stubs=nil, &block
|
43
|
+
log "also " + context_to_string(desc, stubs, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def is? method, actual, expected
|
47
|
+
success = actual == expected
|
48
|
+
|
49
|
+
log 'is ' + method.to_s + ', ' + expected.to_s + '? ' + (success ? 'YES' : "NO, it was '#{actual}'")
|
50
|
+
end
|
51
|
+
|
52
|
+
def compile
|
53
|
+
#do nothing
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def block_to_string &block
|
58
|
+
"(block)"
|
59
|
+
end
|
60
|
+
|
61
|
+
def context_to_string desc=nil, stubs=nil, &block
|
62
|
+
str = ""
|
63
|
+
|
64
|
+
if desc.class == String || desc.class == Symbol
|
65
|
+
str += ":#{desc}: "
|
66
|
+
end
|
67
|
+
|
68
|
+
if desc.class == Hash
|
69
|
+
stubs = desc
|
70
|
+
end
|
71
|
+
|
72
|
+
if stubs
|
73
|
+
str += stubs.map{|k,v| (k.to_s + ': ' + v.to_s)}.join(', ')
|
74
|
+
end
|
75
|
+
|
76
|
+
if block
|
77
|
+
str += block_to_string(&block)
|
78
|
+
end
|
79
|
+
|
80
|
+
return str
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class Console
|
85
|
+
#TODO color_scheme
|
86
|
+
|
87
|
+
def test topic
|
88
|
+
log "\n test ".bright + topic.name.bright.color(250, 37, 115)
|
89
|
+
end
|
90
|
+
|
91
|
+
def given desc=nil, stubs=nil, &block
|
92
|
+
log " given " + context_to_string(desc, stubs, &block)
|
93
|
+
end
|
94
|
+
|
95
|
+
def also desc=nil, stubs=nil, &block
|
96
|
+
log " also " + context_to_string(desc, stubs, &block)
|
97
|
+
end
|
98
|
+
|
99
|
+
def is? method, actual, expected
|
100
|
+
success = actual == expected
|
101
|
+
|
102
|
+
log ' is '.bright + method.to_s + ', ' + expected.to_s.yellow + '? ' + (success ? 'YES'.bright.green : "NO, it was '#{actual}'".bright.red) + "\n\n"
|
103
|
+
end
|
104
|
+
|
105
|
+
def compile
|
106
|
+
#do nothing
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
def block_to_string &block
|
111
|
+
"(block)"
|
112
|
+
end
|
113
|
+
|
114
|
+
def log msg
|
115
|
+
puts msg
|
116
|
+
end
|
117
|
+
|
118
|
+
def context_to_string desc=nil, stubs=nil, &block
|
119
|
+
str = ""
|
120
|
+
|
121
|
+
if desc.class == String || desc.class == Symbol
|
122
|
+
str += ":#{desc}: ".blue
|
123
|
+
end
|
124
|
+
|
125
|
+
if desc.class == Hash
|
126
|
+
stubs = desc
|
127
|
+
end
|
128
|
+
|
129
|
+
if stubs
|
130
|
+
str += stubs.map{|k,v| (k.to_s + ': ' + v.to_s).bright}.join(', ') + ' '
|
131
|
+
end
|
132
|
+
|
133
|
+
if block
|
134
|
+
str += block_to_string(&block)
|
135
|
+
end
|
136
|
+
|
137
|
+
return str
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
data/lib/logic.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
def testme &block
|
2
|
+
extend TestMe
|
3
|
+
block.call
|
4
|
+
end
|
5
|
+
|
6
|
+
module TestMe
|
7
|
+
|
8
|
+
def topic
|
9
|
+
@topic
|
10
|
+
end
|
11
|
+
|
12
|
+
def test topic
|
13
|
+
@topic = nil
|
14
|
+
@before = nil
|
15
|
+
@contexts = {}
|
16
|
+
|
17
|
+
@@formatter ||= Formatter::create(TESTME_FORMAT)
|
18
|
+
@@formatter.test topic
|
19
|
+
|
20
|
+
@topic_class = topic
|
21
|
+
|
22
|
+
if topic.instance_of? Module
|
23
|
+
o = Object.new
|
24
|
+
o.extend topic
|
25
|
+
@topic = Double.new(o)
|
26
|
+
end
|
27
|
+
|
28
|
+
if topic.instance_of? Class
|
29
|
+
@topic = Double.new(topic.new)
|
30
|
+
end
|
31
|
+
|
32
|
+
raise Exception, "Topic needs to be a Class or a Module" unless @topic
|
33
|
+
end
|
34
|
+
|
35
|
+
def given desc=nil, stubs=nil, &block
|
36
|
+
@before.call if @before
|
37
|
+
|
38
|
+
@@formatter.given desc, stubs, &block
|
39
|
+
|
40
|
+
@topic = Double.new(@topic_class.new)
|
41
|
+
|
42
|
+
if desc.class == String || desc.class == Symbol
|
43
|
+
if stubs == nil and block == nil
|
44
|
+
load_context desc
|
45
|
+
return
|
46
|
+
end
|
47
|
+
|
48
|
+
save_context desc, stubs, &block
|
49
|
+
elsif desc.class == Hash
|
50
|
+
stubs = desc
|
51
|
+
end
|
52
|
+
|
53
|
+
set_context stubs, &block
|
54
|
+
end
|
55
|
+
|
56
|
+
def also desc=nil, stubs=nil, &block
|
57
|
+
@@formatter.also desc, stubs, &block
|
58
|
+
|
59
|
+
if desc.class == String || desc.class == Symbol
|
60
|
+
if stubs == nil and block == nil
|
61
|
+
load_context desc
|
62
|
+
return
|
63
|
+
end
|
64
|
+
|
65
|
+
save_context desc, stubs, &block
|
66
|
+
elsif desc.class.name == 'Hash'
|
67
|
+
stubs = desc
|
68
|
+
end
|
69
|
+
|
70
|
+
set_context stubs, &block
|
71
|
+
end
|
72
|
+
|
73
|
+
def is? *args, &block
|
74
|
+
if block
|
75
|
+
method = block
|
76
|
+
result = block.call
|
77
|
+
else
|
78
|
+
if args[0].class == Hash
|
79
|
+
method = args[0].first[0]
|
80
|
+
expected = args[0].first[1]
|
81
|
+
actual = topic.send(method)
|
82
|
+
end
|
83
|
+
|
84
|
+
if args[0].class == Symbol
|
85
|
+
params = args[0].args
|
86
|
+
expected = args[1]
|
87
|
+
actual = topic.send(args[0], *params)
|
88
|
+
|
89
|
+
method = args[0].to_s + (params ? '(' + params.join(',') + ')' : '')
|
90
|
+
end
|
91
|
+
|
92
|
+
expected = true if expected.nil?
|
93
|
+
result = actual == expected
|
94
|
+
end
|
95
|
+
|
96
|
+
@@formatter.is? method, actual, expected
|
97
|
+
|
98
|
+
result
|
99
|
+
end
|
100
|
+
|
101
|
+
def before &block
|
102
|
+
@before = block
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
class Context
|
107
|
+
attr_accessor :name, :block, :stubs
|
108
|
+
end
|
109
|
+
|
110
|
+
def class_from_string(str)
|
111
|
+
str.split('::').inject(Object) do |mod, class_name|
|
112
|
+
mod.const_get(class_name)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def save_context name, stubs, &block
|
117
|
+
c = Context.new
|
118
|
+
c.name = name
|
119
|
+
c.stubs = stubs if stubs
|
120
|
+
c.block = block if block
|
121
|
+
|
122
|
+
@contexts[name] = c
|
123
|
+
end
|
124
|
+
|
125
|
+
def load_context name
|
126
|
+
c = @contexts[name]
|
127
|
+
set_context c.stubs, &c.block
|
128
|
+
end
|
129
|
+
|
130
|
+
def set_context stubs=nil, &block
|
131
|
+
if stubs
|
132
|
+
stubs.each do |k, v|
|
133
|
+
@topic.send("#{k}=".to_sym, v)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
if block
|
138
|
+
block.call
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/symbol.rb
ADDED
data/lib/testme.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
#********************************************************************#
|
2
|
+
#* *#
|
3
|
+
#* Test Me *#
|
4
|
+
#* *#
|
5
|
+
#********************************************************************#
|
6
|
+
|
7
|
+
module TestMe
|
8
|
+
|
9
|
+
# ---------------------------------------------------------------- #
|
10
|
+
# Alleviate the 'the big ball of mud'
|
11
|
+
# Test what you mean
|
12
|
+
# Pure Ruby DSL
|
13
|
+
# ---------------------------------------------------------------- #
|
14
|
+
|
15
|
+
# Retrieve the topic of the test
|
16
|
+
def topic; end
|
17
|
+
# ---------------------------------------------------------------- #
|
18
|
+
|
19
|
+
# Set the topic of the test
|
20
|
+
# ` test Player `
|
21
|
+
def test topic; end
|
22
|
+
# ---------------------------------------------------------------- #
|
23
|
+
|
24
|
+
# Provide a context
|
25
|
+
# stubbing an attribute
|
26
|
+
# ` given name: 'Flavie', class: 'Rogue' `
|
27
|
+
# ` given { topic.name = 'Flavie; topic.class = 'Rogue' } `
|
28
|
+
# calling a method
|
29
|
+
# ` given { topic.talk_to 'Deckard' } `
|
30
|
+
def given *args, █ end
|
31
|
+
# ---------------------------------------------------------------- #
|
32
|
+
|
33
|
+
# Provide a context over the existing context
|
34
|
+
# ` given name: 'Flavie' `
|
35
|
+
# ` also class: 'Rogue' `
|
36
|
+
def also *args, █ end
|
37
|
+
# ---------------------------------------------------------------- #
|
38
|
+
|
39
|
+
# Create an assertion
|
40
|
+
# ` is? name: 'Flavie' `
|
41
|
+
# ` is? :inventory[1], 'Arrows' `
|
42
|
+
# ` is? { topic.inventory(1) == 'Arrows' } `
|
43
|
+
def is? *args, █ end
|
44
|
+
# ---------------------------------------------------------------- #
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
# ---------------------------------------------------------------- #
|
49
|
+
# Inline Testing
|
50
|
+
# ---------------------------------------------------------------- #
|
51
|
+
def testme █ end
|
52
|
+
|
53
|
+
# ---------------------------------------------------------------- #
|
54
|
+
# Optional configuration
|
55
|
+
# ---------------------------------------------------------------- #
|
56
|
+
module TestMe
|
57
|
+
TESTME_DIR = '/test/' unless defined? TESTME_DIR
|
58
|
+
# default: '/test/'
|
59
|
+
|
60
|
+
TESTME_FORMAT = :console unless defined? TESTME_FORMAT
|
61
|
+
# choose how results are displayed
|
62
|
+
# options: :none, :text, :console
|
63
|
+
# default: :console
|
64
|
+
|
65
|
+
TESTME_COLORS = :default unless defined? TESTME_COLORS
|
66
|
+
# color scheme for console output
|
67
|
+
# options: :default
|
68
|
+
# default: :default
|
69
|
+
end
|
70
|
+
|
71
|
+
# ---------------------------------------------------------------- #
|
72
|
+
|
73
|
+
# ---------------------------------------------------------------- #
|
74
|
+
# This is here to disable inline tests at runtime
|
75
|
+
# ---------------------------------------------------------------- #
|
76
|
+
if defined? TESTME_RUNNING
|
77
|
+
# Gems
|
78
|
+
require 'rainbow'
|
79
|
+
require 'colorize'
|
80
|
+
|
81
|
+
# TestMe Library
|
82
|
+
require 'symbol'
|
83
|
+
require 'double'
|
84
|
+
require 'formatter'
|
85
|
+
require 'logic'
|
86
|
+
end
|
87
|
+
# ---------------------------------------------------------------- #
|
data/spec/double_spec.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.expand_path('..', __FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe TestMe::Double do
|
4
|
+
before :each do
|
5
|
+
@topic = TestMe::Double.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#method_missing' do
|
9
|
+
context 'with stub that returns 5' do
|
10
|
+
before :each do
|
11
|
+
@topic.test = 5
|
12
|
+
end
|
13
|
+
|
14
|
+
specify ('stub should == 5') { @topic.test.should == 5 }
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with stub chain that returns 5' do
|
18
|
+
before :each do
|
19
|
+
@topic.test.test2 = 5
|
20
|
+
end
|
21
|
+
|
22
|
+
specify ('stub chain should return 5') { @topic.test.test2.should == 5 }
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'stub chaining over an object' do
|
26
|
+
before :each do
|
27
|
+
class TestObject
|
28
|
+
def hello
|
29
|
+
return 'hello'
|
30
|
+
end
|
31
|
+
|
32
|
+
def world
|
33
|
+
return 'world'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
@topic.test = TestMe::Double.new(TestObject.new)
|
38
|
+
@topic.test.test2 = 5
|
39
|
+
end
|
40
|
+
|
41
|
+
specify ('stub chain should return 5') { @topic.test.test2.should == 5 }
|
42
|
+
specify ('original methods are still callable') { @topic.test.hello.should == 'hello' }
|
43
|
+
|
44
|
+
context 'overriding a method over an object with stub' do
|
45
|
+
before :each do
|
46
|
+
@topic.test.hello = 'goodbye'
|
47
|
+
end
|
48
|
+
|
49
|
+
specify ('method is overridden') { @topic.test.hello.should == 'goodbye' }
|
50
|
+
specify ('original methods are still callable') { @topic.test.world.should == 'world' }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/testme_spec.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
require File.expand_path('..', __FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
class Mock; end
|
4
|
+
|
5
|
+
describe 'TestMe' do
|
6
|
+
before :each do
|
7
|
+
extend TestMe
|
8
|
+
test Mock
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#topic' do
|
12
|
+
specify('is created') {topic.class.name.should == 'TestMe::Double'}
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#given' do
|
16
|
+
it 'stubs one' do
|
17
|
+
given name: 'bob'
|
18
|
+
topic.name.should == 'bob'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'stubs multiple' do
|
22
|
+
given name: 'bob', surname: 'smith'
|
23
|
+
topic.name.should == 'bob'
|
24
|
+
topic.surname.should == 'smith'
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when given is repeated' do
|
28
|
+
before :each do
|
29
|
+
given name: 'bob'
|
30
|
+
given surname: 'smith'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should clear previous context' do
|
34
|
+
topic.respond_to?(:name).should == false
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should create a new context' do
|
38
|
+
topic.surname.should == 'smith'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when block is given' do
|
43
|
+
before :each do
|
44
|
+
given {topic.name = 'bob'}
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'stubs one' do
|
48
|
+
given name: 'bob'
|
49
|
+
topic.name.should == 'bob'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'when description is given' do
|
54
|
+
before :each do
|
55
|
+
given :name_is_bob, name: 'bob'
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should save the context' do
|
59
|
+
given name: 'fred'
|
60
|
+
given :name_is_bob
|
61
|
+
|
62
|
+
topic.name.should == 'bob'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#also' do
|
68
|
+
it 'retains the context' do
|
69
|
+
given name: 'bob'
|
70
|
+
|
71
|
+
topic.name.should == 'bob'
|
72
|
+
topic.surname.should_not == 'smith'
|
73
|
+
|
74
|
+
also surname: 'smith'
|
75
|
+
|
76
|
+
topic.name.should == 'bob'
|
77
|
+
topic.surname.should == 'smith'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#is?' do
|
82
|
+
context 'when name is bob' do
|
83
|
+
before :each do
|
84
|
+
given name: 'bob'
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'asserts name is bob' do
|
88
|
+
result = is? name: 'bob'
|
89
|
+
result.should == true
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'asserts name is not fred' do
|
93
|
+
result = is? name: 'fred'
|
94
|
+
result.should_not == true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'when Block is given' do
|
99
|
+
before :each do
|
100
|
+
given name: 'bob'
|
101
|
+
@result = is? {topic.name == 'bob'}
|
102
|
+
end
|
103
|
+
|
104
|
+
specify('asserts correct result') { @result.should == true }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'class methods' do
|
109
|
+
before :each do
|
110
|
+
class ClassMethods
|
111
|
+
def self.hello
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
test ClassMethods, :self
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
context 'modified topic' do
|
121
|
+
|
122
|
+
before :each do
|
123
|
+
class ArgCounter
|
124
|
+
def count *args; return args.size; end
|
125
|
+
def true; return true; end
|
126
|
+
end
|
127
|
+
|
128
|
+
test ArgCounter
|
129
|
+
end
|
130
|
+
|
131
|
+
describe '#is?' do
|
132
|
+
context 'when Symbol is given' do
|
133
|
+
specify('parses 0 arguments'){(is? :true).should == true}
|
134
|
+
specify('parses 0 arguments'){(is? :count, 0).should == true}
|
135
|
+
specify('parses 1 argument with auto-true'){(is? :count[1]).should == false}
|
136
|
+
specify('parses 1 argument (string) with auto-true'){(is? :count['1']).should == false}
|
137
|
+
specify('parses 1 argument with answer'){(is? :count[1], 1).should == true}
|
138
|
+
specify('parses multiple arguments'){(is? :count[1,1,1], 3).should == true}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#test' do
|
145
|
+
before :each do
|
146
|
+
module Mock2
|
147
|
+
def hello; return 'hello'; end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'test double of Module' do
|
152
|
+
test Mock2
|
153
|
+
topic.hello.should == 'hello'
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
|
data/test/app.test.rb
ADDED
File without changes
|
data/testme.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.unshift(File.expand_path("../lib", __FILE__))
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'testme'
|
7
|
+
|
8
|
+
s.version = begin
|
9
|
+
revision = (`git log --pretty=format:'' | wc -l`.to_f / 20).round(2).to_s
|
10
|
+
"0.#{revision}"
|
11
|
+
end
|
12
|
+
|
13
|
+
s.date = Date.today.to_s
|
14
|
+
s.authors = ['Daniel Shuey']
|
15
|
+
s.email = ['daniel.shuey@gmail.com']
|
16
|
+
s.summary = 'Minimalistic testing framework'
|
17
|
+
|
18
|
+
s.description = <<-END
|
19
|
+
Minimalistic testing framework
|
20
|
+
END
|
21
|
+
|
22
|
+
s.homepage = 'http://github.com/danielshuey/testme'
|
23
|
+
|
24
|
+
s.files = `git ls-files`.split("\n")
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
26
|
+
s.require_paths = %w(lib)
|
27
|
+
|
28
|
+
s.add_runtime_dependency('rainbow', ['>= 1.1.3'])
|
29
|
+
s.add_runtime_dependency('colorize', ['>= 0.5.8'])
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: testme
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.6
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Daniel Shuey
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rainbow
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.1.3
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.1.3
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: colorize
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.5.8
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.5.8
|
46
|
+
description: ! ' Minimalistic testing framework
|
47
|
+
|
48
|
+
'
|
49
|
+
email:
|
50
|
+
- daniel.shuey@gmail.com
|
51
|
+
executables:
|
52
|
+
- testme
|
53
|
+
extensions: []
|
54
|
+
extra_rdoc_files: []
|
55
|
+
files:
|
56
|
+
- .gitignore
|
57
|
+
- .rspec
|
58
|
+
- .rvmrc
|
59
|
+
- .testme
|
60
|
+
- Gemfile
|
61
|
+
- LICENSE
|
62
|
+
- README.md
|
63
|
+
- bin/testme
|
64
|
+
- lib/double.rb
|
65
|
+
- lib/formatter.rb
|
66
|
+
- lib/logic.rb
|
67
|
+
- lib/symbol.rb
|
68
|
+
- lib/testme.rb
|
69
|
+
- spec/double_spec.rb
|
70
|
+
- spec/spec_helper.rb
|
71
|
+
- spec/testme_spec.rb
|
72
|
+
- test/app.test.rb
|
73
|
+
- testme.gemspec
|
74
|
+
homepage: http://github.com/danielshuey/testme
|
75
|
+
licenses: []
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
hash: 1026832301
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
segments:
|
96
|
+
- 0
|
97
|
+
hash: 1026832301
|
98
|
+
requirements: []
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.8.24
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: Minimalistic testing framework
|
104
|
+
test_files: []
|