riot 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Justin Knowlden, Thumble Monks
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,315 @@
1
+ # Riot
2
+
3
+ An extremely fast, expressive, and context-driven unit-testing framework. Protest the slow test.
4
+
5
+ ### Note on speed
6
+
7
+ I have done a really simple benchmarking, but right now, Riot is running about **10-times** faster than Test::unit and thusly Shoulda:
8
+
9
+ $ ruby test/benchmark/simple_context_and_assertions.rb
10
+
11
+ Rehearsal ----------------------------------------------
12
+ Riot 0.140000 0.010000 0.150000 ( 0.156170)
13
+ Test::Unit 1.460000 0.000000 1.460000 ( 1.495990)
14
+ Shoulda 1.490000 0.010000 1.500000 ( 1.514745)
15
+ ------------------------------------- total: 3.110000sec
16
+
17
+ user system total real
18
+ Riot 0.130000 0.000000 0.130000 ( 0.139770)
19
+ Test::Unit 1.600000 0.010000 1.610000 ( 1.627493)
20
+ Shoulda 1.610000 0.010000 1.620000 ( 1.655394)
21
+
22
+ Loaded suite test/benchmark/simple_context_and_assertions
23
+
24
+ "Is it valid?", you ask. *I* think it is. I ain't no cheater, but I might be delusional.
25
+
26
+ #### Example: Basic booleans
27
+
28
+ **NOTE:** For no specific reason, I'm going to use an ActiveRecord model in the following examples.
29
+
30
+ At it's very basic, Riot simply tries to assert that an expression is true or false. Riot does this through its `asserts` or `should` tests. An `asserts` test will pass if the result of running the test is neither `nil` or `false`. A `should` test does the exact same thing - they are in fact aliases. The only difference is in the way you write the assertion.
31
+
32
+ For instance, given a test file named `foo_test.rb`, you might have the following code in it:
33
+
34
+ require 'riot'
35
+
36
+ context "a new user" do
37
+ setup { @user = User.new }
38
+ asserts("that it is not yet created") { @user.new_record? }
39
+ end
40
+
41
+ Notice that you do not define a class anywhere. That would be the entire contents of that test file. If you wanted to use a `should` instead, you could say this:
42
+
43
+ require 'riot'
44
+
45
+ context "a new user" do
46
+ setup { @user = User.new }
47
+ should("not be created") { @user.new_record? }
48
+ end
49
+
50
+ Sometimes it's more clear to say "this **should** be that" and sometimes it's better to say "**asserts** this is that". I promise you that Riot will get no more redundant than this, but also that besides speed, Riot will aim at being expressive with a minimal amount of syntax.
51
+
52
+ I'm going to use `asserts` for the rest of this introduction, but you should know that you can replace any instance of `asserts` with `should` and nothing would change.
53
+
54
+ #### Example: Equality
55
+
56
+ One of the most common assertions you will (or do already) utilize is that of equality; is this equal to that? Riot supports this in a slightly different manner than most other frameworks. With Riot, you add the expectation to the assertion itself.
57
+
58
+ For example:
59
+
60
+ context "a new user" do
61
+ setup { @user = User.new(:email => 'foo@bar.com') }
62
+ asserts("email address") { @user.email }.equals('foo@bar.com')
63
+ end
64
+
65
+ Here, you should begin to notice that tests themselves return the actual value. You do not write assertions into the test. Assertions are "aspected" onto the test. If the test above did not return 'foo@bar.com' for `@user.email`, the assertion would have failed.
66
+
67
+ The `equals` modifier works with any type of value, including nil's. However, if you wanted to test for nil explicitly, you could simply do this:
68
+
69
+ context "a new user" do
70
+ setup { @user = User.new }
71
+ asserts("email address") { @user.email }.nil
72
+ end
73
+
74
+ Notice the `nil` modifier added to asserts. Also notice how the test almost reads as "a new user asserts email address *is* nil". With Test::Unit, you would have probably written:
75
+
76
+ class UserTest < Test::Unit::TestCase
77
+ def setup
78
+ @user = User.new
79
+ end
80
+
81
+ def test_email_address_is_nil
82
+ assert_nil @user.email
83
+ end
84
+ end
85
+
86
+ Which, to me, seems like a redundancy. The test already says it's nil! Maybe Shoulda woulda helped:
87
+
88
+ class UserTest < Test::Unit::TestCase
89
+ def setup
90
+ @user = User.new
91
+ end
92
+
93
+ should "have nil email" { assert_nil @user.email }
94
+ end
95
+
96
+ In my opinion, the same redundancy exists. Sure, I could write a macro like `should_be_nil {@user.email}`, but the redundancy exists in the framework itself.
97
+
98
+ #### Example: Matches
99
+
100
+ If you need to assert that a test result matches a regular expression, use the `matches` modifier like so:
101
+
102
+ context "a new user" do
103
+ setup { @user = User.new }
104
+
105
+ # I'm a contrived example
106
+ asserts("random phone number") { @user.random_phone_number }.matches(/^\d{3}-\d{3}-\d{4}$/)
107
+ end
108
+
109
+ #### Example: Raises
110
+
111
+ Sometimes, your test raises an exception that you actually expected.
112
+
113
+ context "a new user" do
114
+ setup { @user = User.new }
115
+ asserts("invalid data") { @user.save! }.raises(ActiveRecord::RecordInvalid)
116
+ end
117
+
118
+ #### Example: Kind Of
119
+
120
+ When you want to test that an expression returns an object of an expected type:
121
+
122
+ context "a new user" do
123
+ setup { @user = User.new }
124
+ asserts("its balance") { @user.balance }.kind_of(Currency)
125
+ end
126
+
127
+ #### Example: Nested contexts
128
+
129
+ Oh yeah, Riot does those, too. The `setup` from each parent is "loaded" into the context and then the context is executed as normal. Test naming is a composite of the parent contexts' names. Here, we'll do a little Sinatra testing (see below for instructions on how to make it Riot work seamlessly with Sinatra tests).
130
+
131
+ context "My App:" do
132
+ setup { @app = MyApp }
133
+
134
+ context "get /" do
135
+ setup { get '/' }
136
+ # ...
137
+ # assertions
138
+
139
+ context "renders a body" do
140
+ setup { @body = last_response.body }
141
+ # ...
142
+ # assertions
143
+ end
144
+ end
145
+
146
+ context "get /books/1" do
147
+ setup { get '/books/1' }
148
+ # ...
149
+ # assertions
150
+ end
151
+ end
152
+
153
+ #### More examples/documentation
154
+
155
+ There are many more basic assertion modifiers to come. See below for writing Riot extensions if you want to help out.
156
+
157
+ See the TODO section for everything that's missing.
158
+
159
+ Also, see [the wiki](http://wiki.github.com/thumblemonks/riot) for more examples and documentation.
160
+
161
+ ## You say, "OMG! Why did you write this?"
162
+
163
+ ### Some background, I guess
164
+
165
+ You start a new project. You get all excited. You're adding tests. You're adding factories. You're fixturating your setups. You're adding more tests. Your tests start slowing down, but you need to keep pushing because your backlog has a lot of new, nifty features in it. You've got 3000+ lines of test code, 2000+ assertions. Your tests are annoyingly slow. Your tests have become a burden.
166
+
167
+ I hate this and it happens a lot, even when I'm conscience that it's happening. Even when I'm not hitting the database and I'm mocking the crap out my code.
168
+
169
+ I really, really hate slow test suites.
170
+
171
+ #### Did ... you look at Shoulda
172
+
173
+ I should say that I love Shoulda in theory and in practice. It changed the way I coded. I added macros all over the place. I built macros into the gems I wrote for the gem itself. But, alas, Shoulda is slow. Shoulda is based on Test::Unit. Shoulda reruns setups for every should. Shoulda could make my life even easier with even more expressiveness.
174
+
175
+ #### Did ... you look at RSpec
176
+
177
+ :| yes, no, I don't care. It's slow, too. Anyways, I was at [ObjectMentor](http://objectmentor.com) many, many moons ago when Dave Astels (accompanied by David Chelimsky) were explaining this brand new approach to testing called BDD. Mr. Astels explained to us that we if we already understood TDD, then BDD wouldn't help a bunch. Why argue with him?
178
+
179
+ ### How Riot is the same
180
+
181
+ 1. It defines a context
182
+ 1. It prints .'s, F's, and E's when tests pass, fail, or error
183
+ 1. It tells you how long it took to run just the tests
184
+ 1. Contexts can be nested and setups inherited
185
+
186
+ ### How Riot is different
187
+
188
+ Riot differs primarily in that it does not rerun setup for each test in a context. I know this is going to shock and awe a lot of folks. However, over the past several years of my doing TDD in some capacity or another, there are certain habits I have tried to pick up on any many others I have tried to drop.
189
+
190
+ For instance, I believe that no assertion should mangle the context of the test data it is running in. Following this allows me to require setup be run only once for a collection of related assertions. Even in a nested context where setups are inherited, the setup's are called only once per the specific context.
191
+
192
+ Following all of this allows me to have very fast tests (so far).
193
+
194
+ ...
195
+
196
+ Riot is also different in that assertions are not added to the test block. Each test block is it's own assertion (and assertion block). Whatever the result is of processing an assertion block will be used to determine if an assertion passed or failed. Each assertion block can have a specific validator tied to it for asserting any number of things, like: the result of the test **equals** some expected value, the result of the test **matches** some expected expression, or the result of the test **raises** some exception.
197
+
198
+ I like this approach because I only want to test one thing, but that one thing may require some work on my behalf to get the value. Riot does not let me cheat in this regard. There is no way for me to add more than one assertion to an assertion block.
199
+
200
+ ...
201
+
202
+ I imagine this approach will persuade many of you to avoid Riot altogether. I don't blame you. A few years ago I would have avoided it, too. As of this writing though, I have ported Chicago and Slvu (which were previously written in Test::Unit + Shoulda) to Riot, cut the number of lines of code in almost half, definitely more than halved the amount of time the tests took to run, and did so in less than half a day (I was building Riot while porting them :).
203
+
204
+ ## Running tests
205
+
206
+ Create or modify your existing Rakefile to define a test task like so:
207
+
208
+ desc 'Default task: run all tests'
209
+ task :default => [:test]
210
+
211
+ desc "Run all tests"
212
+ task :test do
213
+ require 'riot'
214
+ $:.concat ['./lib', './test']
215
+ Dir.glob("./test/*_test.rb").each { |test| require test }
216
+ Riot.report
217
+ end
218
+
219
+ Then, from the command line, you only need to run `rake` or `rake test`. Please make sure to remove all references to any other testing frameworks before running tests. For instance, do not require `test/unit`, `shoulda`, `minitest`, or anything else like it.
220
+
221
+ ### With Sinatra
222
+
223
+ Riot definitely works with the latest Sinatra. I personally use it to run tests for [Chicago](http://github.com/thumblemonks/chicago) and [Slvu](http://github.com/jaknowlden/slvu). Setup is pretty easy and very much like getting your tests to run with Test::Unit. In a test helper file that gets loaded into all of your tests (that need it), enter the following:
224
+
225
+ require 'riot'
226
+ class Riot::Context
227
+ include Rack::Test::Methods
228
+ def app; @app; end
229
+ end
230
+
231
+ And then define a context (or many) for testing your Sinatra app. For instance:
232
+
233
+ require 'test_helper'
234
+
235
+ context 'Some App' do
236
+ setup { @app = SomeApp }
237
+
238
+ context "/index" do
239
+ setup { get '/' }
240
+ # ...
241
+ end
242
+ end
243
+
244
+ Make sure to check out the Riot + Sinatra testing macros in Chicago.
245
+
246
+ ### With Rails
247
+
248
+ It's doubtful that Riot works with Rails very easily as Riot completely replaces Test::Unit. I haven't tried it yet, but intend to with my next new Rails project. Porting would probably take some time unless you only have a few test cases. Porting is made somewhat easier if you're already using Shoulda; you can replace the `TestCase` definition with a `context` of the same name as the class under test I suppose.
249
+
250
+ ## Extending Riot with Macros
251
+
252
+ To extend Riot, similar to how you would with Shoulda, you simply need to include your methods into the `Riot::Context` class. For example, let's say you wanted to add a helpful macro for asserting the response status of some HTTP result in Sinatra. You could do this easily by defining your macro like so:
253
+
254
+ module Custom
255
+ module Macros
256
+ def asserts_response_status(expected)
257
+ asserts("response status is #{expected}") do
258
+ last_response.status
259
+ end.equals(expected)
260
+ end
261
+ end # Macros
262
+ end # Custom
263
+ Riot::Context.instance_eval { include Custom::Macros }
264
+
265
+ And then in your actual test, you might do the following:
266
+
267
+ context 'Some App' do
268
+ setup { @app = SomeApp }
269
+
270
+ context "/index" do
271
+ setup { get '/' }
272
+ asserts_response_status 200
273
+ end
274
+ end
275
+
276
+ **COMING SOON:** Riot will look into test/riot\_macros, but not today.
277
+
278
+ #### Assertion macros
279
+
280
+ If you want to add special macros to an Assertion, this is as easy as adding them to a Context. Similar to Context macros, Assertion macros are included into the Assertion class.
281
+
282
+ For instance, let's say you wanted to add a macro for verifying that the result of an assertion is the same kind\_of object as you would expect. You would define the macro like so:
283
+
284
+ module Custom
285
+ module AssertionMacros
286
+ def kind_of(expected_class)
287
+ actual.kind_of?(expected) || failure("expected kind of #{expected}, not #{actual.inspect}")
288
+ end
289
+ end # AssertionMacros
290
+ end # Custom
291
+ Riot::Assertion.instance_eval { include Custom::AssertionMacros }
292
+
293
+ And in your context, you would use it like so:
294
+
295
+ context "example" do
296
+ asserts("a hash is defined") { {:foo => 'bar'} }.kind_of(Hash)
297
+ end
298
+
299
+ Notice in the new macro we defined the use of the magical **actual** variable. `actual` is evaluated when the assertion is defined and made available to any Assertion macro. If you think you might want to chain assertions checks together, know that only the last failure will get recorded.
300
+
301
+ ## TODO
302
+
303
+ TONS OF STUFF
304
+
305
+ 1. Documentation about the `topic` method
306
+ 1. Better documentation for everything
307
+ 1. Refactor reporting; some abstracting is needed for recording a result (for instance)
308
+ 1. Need to know where in backtrace a test failed (line number, etc.); i.e. backtrace filtering for clarity
309
+ 1. More assertion macros: throws, etc.
310
+ 1. Aliases for context "with, without, when, ..."; add those words to test description
311
+ 1. Optimization and simplification (ex. flog is complaining print\_result\_stack)
312
+ 1. Better error messages?
313
+ 1. Perhaps: Multiple setup blocks in one context
314
+ 1. Perhaps: association macro chaining
315
+ 1. Perhaps: Uhhhhh ... a teardown method (only maybe. not sold :)
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ task :default => [:test]
5
+
6
+ desc "Run tests"
7
+ task :test do
8
+ $:.concat ['./test', './lib']
9
+ Dir.glob("./test/*_test.rb").each { |test| require test }
10
+ Riot.report
11
+ end
12
+
13
+ desc "Run Flog against library (except tests)"
14
+ task :flog do
15
+ puts %x[find ./lib -name *.rb | xargs flog]
16
+ end
17
+
18
+ desc "Run Flay against library (except tests)"
19
+ task :flay do
20
+ puts %x[find ./lib -name *.rb | xargs flay]
21
+ end
22
+
23
+ desc "Run Roodi against library (except tests)"
24
+ task :roodi do
25
+ puts %x[find ./lib -name *.rb | xargs roodi]
26
+ end
@@ -0,0 +1,68 @@
1
+ require 'riot/report'
2
+ require 'riot/context'
3
+ require 'riot/assertion'
4
+ require 'riot/macros'
5
+
6
+ module Riot
7
+ #
8
+ # Initializing logic
9
+ def self.contexts
10
+ @contexts ||= []
11
+ end
12
+
13
+ def self.context(description, reporter = nil, parent = nil, &block)
14
+ reporter ||= self.reporter
15
+ context = Context.new(description, reporter, parent)
16
+ reporter.time { context.instance_eval(&block) }
17
+ context.report # Results get buffered this way, not necessarily the best
18
+ (contexts << context).last
19
+ end
20
+
21
+ def self.dequeue_context(context)
22
+ contexts.delete(context)
23
+ end
24
+
25
+ def self.report
26
+ reporter.results
27
+ at_exit { exit false unless reporter.passed? }
28
+ end
29
+
30
+ #
31
+ # Reporter
32
+
33
+ def self.reporter; @reporter ||= TextReport.new; end
34
+ def self.reporter=(report); @reporter = report; end
35
+ def self.silently!; @silently = true; end
36
+ def self.silently?; @silently || false; end
37
+
38
+ #
39
+ # Exception
40
+
41
+ class Failure < Exception
42
+ attr_reader :assertion, :context
43
+ def initialize(message, assertion)
44
+ super(message)
45
+ @assertion = assertion
46
+ end
47
+
48
+ def contextualize(ctx)
49
+ @context = ctx
50
+ self
51
+ end
52
+
53
+ def print_stacktrace?; false; end
54
+ end
55
+ class Error < Failure
56
+ def initialize(message, assertion, error)
57
+ super(message, assertion)
58
+ set_backtrace(error.backtrace)
59
+ end
60
+ def print_stacktrace?; true; end
61
+ end
62
+ end # Riot
63
+
64
+ module Kernel
65
+ def context(*args, &block)
66
+ Riot.context(*args, &block)
67
+ end
68
+ end # Kernel
@@ -0,0 +1,35 @@
1
+ module Riot
2
+
3
+ class Assertion
4
+ attr_reader :raised, :to_s, :description
5
+ def initialize(description, situation, &block)
6
+ @description = @to_s = description
7
+ actualize(situation, &block)
8
+ end
9
+
10
+ def actual
11
+ @default_failure = @failure = nil if @default_failure
12
+ @actual
13
+ end
14
+
15
+ def fail(message)
16
+ @failure = Failure.new(message, self) unless errored?
17
+ end
18
+
19
+ def failed?; !@failure.nil?; end
20
+ def errored?; !@raised.nil?; end
21
+ def passed?; !(failed? || errored?); end
22
+ def result; @failure || error; end
23
+ private
24
+ def actualize(situation, &block)
25
+ @actual = situation.instance_eval(&block)
26
+ @default_failure = fail("expected true, not #{@actual.inspect}") unless @actual
27
+ rescue Exception => e
28
+ @raised = e
29
+ end
30
+
31
+ def error
32
+ Error.new("errored with #{@raised}", self, @raised) if errored?
33
+ end
34
+ end # Assertion
35
+ end # Riot
@@ -0,0 +1,59 @@
1
+ module Riot
2
+ class Situation; end
3
+
4
+ class Context
5
+ # The protein
6
+ attr_reader :description, :assertions, :situation
7
+ def initialize(description, reporter, parent=nil)
8
+ @description, @reporter = description, reporter
9
+ @assertions = []
10
+ @parent = parent
11
+ @situation = Situation.new
12
+ bootstrap(@situation)
13
+ end
14
+
15
+ def bootstrap(a_situation)
16
+ @parent.bootstrap(a_situation) if @parent # Walk it out
17
+ induce_local_setup(a_situation)
18
+ end
19
+
20
+ # something smelly between setup() and bootstrap()
21
+ def setup(&block)
22
+ @setup = block
23
+ make_situation_topical(induce_local_setup(situation))
24
+ end
25
+
26
+ # DSLee stuff
27
+ def context(description, &block) Riot.context(description, @reporter, self, &block); end
28
+ def asserts(description, &block) new_assertion("asserts #{description}", &block); end
29
+ def should(description, &block) new_assertion("should #{description}", &block); end
30
+
31
+ # In conclusion
32
+ def report
33
+ # we should just be passing assertions to the reporter and building better descriptions earlier
34
+ assertions.each do |assertion|
35
+ if assertion.passed?
36
+ @reporter.passed
37
+ else
38
+ result = assertion.result.contextualize(self)
39
+ @reporter.send( (assertion.errored? ? :errored : :failed), result)
40
+ end
41
+ end
42
+ end
43
+
44
+ def to_s; @to_s ||= [@parent.to_s, @description].join(' ').strip; end
45
+ private
46
+ def new_assertion(description, &block)
47
+ (assertions << Assertion.new(description, @situation, &block)).last
48
+ end
49
+
50
+ def induce_local_setup(a_situation)
51
+ a_situation.instance_eval(&@setup) if @setup
52
+ end
53
+
54
+ def make_situation_topical(topic)
55
+ situation.instance_variable_set(:@topic, topic)
56
+ situation.instance_eval { def topic; @topic; end }
57
+ end
58
+ end # Context
59
+ end # Riot
@@ -0,0 +1,43 @@
1
+ module Riot
2
+ module AssertionMacros
3
+ # Asserts that the result of the test equals the expected value
4
+ # asserts("test") { "foo" }.equals("foo")
5
+ # should("test") { "foo" }.equals("foo")
6
+ def equals(expected)
7
+ expected == actual || fail("expected #{expected.inspect}, not #{actual.inspect}")
8
+ end
9
+
10
+ # Asserts that the result of the test is nil
11
+ # asserts("test") { nil }.nil
12
+ # should("test") { nil }.nil
13
+ def nil
14
+ actual.nil? || fail("expected nil, not #{actual.inspect}")
15
+ end
16
+
17
+ # Asserts that the test raises the expected Exception
18
+ # asserts("test") { raise My::Exception }.raises(My::Exception)
19
+ # should("test") { raise My::Exception }.raises(My::Exception)
20
+ def raises(expected)
21
+ fail("should have raised #{expected}, but raised nothing") unless raised
22
+ fail("should have raised #{expected}, not #{error.class}") unless expected == raised.class
23
+ @raised = nil
24
+ end
25
+
26
+ # Asserts that the result of the test equals matches against the proved expression
27
+ # asserts("test") { "12345" }.matches(/\d+/)
28
+ # should("test") { "12345" }.matches(/\d+/)
29
+ def matches(expected)
30
+ expected = %r[#{Regexp.escape(expected)}] if expected.kind_of?(String)
31
+ actual =~ expected || fail("expected #{expected.inspect} to match #{actual.inspect}")
32
+ end
33
+
34
+ # Asserts that the result of the test is an object that is a kind of the expected type
35
+ # asserts("test") { "foo" }.kind_of(String)
36
+ # should("test") { "foo" }.kind_of(String)
37
+ def kind_of(expected)
38
+ actual.kind_of?(expected) || fail("expected kind of #{expected}, not #{actual.inspect}")
39
+ end
40
+ end # AssertionMacros
41
+ end # Riot
42
+
43
+ Riot::Assertion.instance_eval { include Riot::AssertionMacros }
@@ -0,0 +1,79 @@
1
+ require 'stringio'
2
+
3
+ module Riot
4
+ class Report
5
+ attr_reader :bad_results, :passes, :failures, :errors, :time_taken
6
+ def initialize
7
+ @bad_results = []
8
+ @passes, @failures, @errors, @time_taken = 0, 0, 0, 0.0
9
+ end
10
+
11
+ def passed?; failures + errors == 0; end
12
+ def assertions; passes + failures + errors; end
13
+
14
+ def time(&block)
15
+ @start = Time.now
16
+ yield
17
+ @time_taken += (Time.now - @start).to_f
18
+ end
19
+
20
+ def passed; @passes += 1; end
21
+
22
+ def failed(failure)
23
+ @failures += 1
24
+ @bad_results << failure
25
+ end
26
+
27
+ def errored(error)
28
+ @errors += 1
29
+ @bad_results << error
30
+ end
31
+ end # Report
32
+
33
+ class TextReport < Report
34
+ def initialize(writer=nil)
35
+ super()
36
+ @writer ||= (Riot.silently? ? StringIO.new : STDOUT)
37
+ end
38
+
39
+ def passed
40
+ super
41
+ @writer.print('.')
42
+ end
43
+
44
+ def failed(failure)
45
+ super
46
+ @writer.print('F')
47
+ end
48
+
49
+ def errored(error)
50
+ super
51
+ @writer.print('E')
52
+ end
53
+
54
+ def results
55
+ @writer.puts "\n\n"
56
+ print_result_stack
57
+ format = "%d assertions, %d failures, %d errors in %s seconds"
58
+ @writer.puts format % [assertions, failures, errors, ("%0.6f" % time_taken)]
59
+ end
60
+ private
61
+ def print_result_stack
62
+ bad_results.each_with_index do |result, idx|
63
+ @writer.puts render_result(idx + 1, result)
64
+ @writer.puts " " + result.backtrace.join("\n ") if result.print_stacktrace?
65
+ @writer.puts "\n\n"
66
+ end
67
+ end
68
+
69
+ def render_result(idx, result)
70
+ format_args = [idx, result.context.to_s, result.assertion.to_s, result.to_s]
71
+ "#%d - %s %s: %s" % format_args
72
+ end
73
+ end # TextReport
74
+
75
+ class NilReport < Report
76
+ def results; end
77
+ def time(&block); yield; end
78
+ end # NilReport
79
+ end # Riot
@@ -0,0 +1,33 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "riot"
3
+ s.version = "0.9.0"
4
+ s.date = "2009-10-03"
5
+ s.summary = "An extremely fast, expressive, and context-driven unit-testing framework"
6
+ s.email = %w[gus@gusg.us]
7
+ s.homepage = "http://github.com/thumblemonks/protest"
8
+ s.description = "An extremely fast, expressive, and context-driven unit-testing framework. A replacement for all other testing frameworks"
9
+ s.authors = %w[Justin\ Knowlden]
10
+
11
+ s.has_rdoc = false
12
+ s.rdoc_options = ["--main", "README.markdown"]
13
+ s.extra_rdoc_files = ["README.markdown"]
14
+
15
+ # run git ls-files to get an updated list
16
+ s.files = %w[
17
+ MIT-LICENSE
18
+ README.markdown
19
+ lib/riot.rb
20
+ lib/riot/assertion.rb
21
+ lib/riot/context.rb
22
+ lib/riot/macros.rb
23
+ lib/riot/report.rb
24
+ riot.gemspec
25
+ ]
26
+
27
+ s.test_files = %w[
28
+ Rakefile
29
+ test/assertion_test.rb
30
+ test/benchmark/simple_context_and_assertions.rb
31
+ test/context_test.rb
32
+ ]
33
+ end
@@ -0,0 +1,66 @@
1
+ require 'riot'
2
+
3
+ fake_context = Object.new # It works ... so, why not?
4
+
5
+ context "basic assertion:" do
6
+ should "have a description" do
7
+ Riot::Assertion.new("i will pass", fake_context).to_s
8
+ end.equals("i will pass")
9
+
10
+ asserts "pass? is true when assertion passed" do
11
+ Riot::Assertion.new("i will pass", fake_context) { true }.passed?
12
+ end
13
+
14
+ asserts "failure? is true when assertion does not pass" do
15
+ Riot::Assertion.new("i will pass", fake_context) { false }.failed?
16
+ end
17
+
18
+ asserts "error? is true when an unexpected Exception is raised" do
19
+ Riot::Assertion.new("error", fake_context) { raise Exception, "blah" }.errored?
20
+ end
21
+ end
22
+
23
+ context "equals assertion:" do
24
+ asserts "result equals expectation" do
25
+ Riot::Assertion.new("i will pass", fake_context) { "foo bar" }.equals("foo bar")
26
+ end
27
+
28
+ should "raise a Failure if results don't equal each other" do
29
+ Riot::Assertion.new("failure", fake_context) { "bar" }.equals("foo")
30
+ end.kind_of(Riot::Failure)
31
+ end # equals assertion
32
+
33
+ context "nil assertion:" do
34
+ asserts("result is nil") { Riot::Assertion.new("foo", fake_context) { nil }.nil }
35
+ should "raise a Failure if not nil" do
36
+ Riot::Assertion.new("foo", fake_context) { "a" }.nil
37
+ end.kind_of(Riot::Failure)
38
+ end # nil assertion
39
+
40
+ context "raises assertion:" do
41
+ should("raise an Exception") { raise Exception }.raises(Exception)
42
+ end # raises assertion
43
+
44
+ context "matching assertion:" do
45
+ asserts "result matches expression" do
46
+ Riot::Assertion.new("foo", fake_context) { "a" }.matches(%r[.])
47
+ end.equals(0)
48
+
49
+ should "raise a Failure if result does not match" do
50
+ Riot::Assertion.new("foo", fake_context) { "" }.matches(%r[.])
51
+ end.kind_of(Riot::Failure)
52
+
53
+ should "return the result of a matching operation" do
54
+ Riot::Assertion.new("foo", fake_context) { "a" }.matches("a")
55
+ end.equals(0)
56
+ end # maching assertion
57
+
58
+ context "kind_of assertion:" do
59
+ asserts "specific result is a kind of String" do
60
+ Riot::Assertion.new("foo", fake_context) { "a" }.kind_of(String)
61
+ end
62
+
63
+ should "raise a Failure if not a kind of String" do
64
+ Riot::Assertion.new("foo", fake_context) { 0 }.kind_of(String)
65
+ end.kind_of(Riot::Failure)
66
+ end # kind_of assertion
@@ -0,0 +1,76 @@
1
+ $:.concat ['./lib']
2
+ require 'benchmark'
3
+
4
+ #
5
+ # Model
6
+
7
+ class Room
8
+ attr_reader :name
9
+ def initialize(name)
10
+ @name = name
11
+ end
12
+ end
13
+
14
+ #
15
+ # Riot
16
+
17
+ require 'riot'
18
+ Riot.silently! # Do this before any contexts are defined
19
+
20
+ context "a room" do
21
+ setup { @room = Room.new("bed") }
22
+
23
+ asserts("name") { @room.name }.equals("bed")
24
+ end # a room
25
+
26
+ #
27
+ # Test::Unit
28
+
29
+ require 'test/unit'
30
+ Test::Unit.run = false
31
+
32
+ require 'test/unit/ui/console/testrunner'
33
+
34
+ class RoomTest < Test::Unit::TestCase
35
+ def setup
36
+ @room = Room.new("bed")
37
+ end
38
+
39
+ def test_room_should_be_named_bed
40
+ assert_equal "bed", @room.name
41
+ end
42
+ end
43
+
44
+ #
45
+ # Shoulda
46
+
47
+ require 'rubygems'
48
+ require 'shoulda'
49
+
50
+ class ShouldaRoomTest < Test::Unit::TestCase
51
+ def setup
52
+ @room = Room.new("bed")
53
+ end
54
+
55
+ should("be named 'bed'") { assert_equal "bed", @room.name }
56
+ end
57
+
58
+ #
59
+ # Benchmarking
60
+
61
+ n = 100 * 100
62
+
63
+ Benchmark.bmbm do |x|
64
+ x.report("Riot") do
65
+ n.times { Riot.report }
66
+ end
67
+
68
+ x.report("Test::Unit") do
69
+ n.times { Test::Unit::UI::Console::TestRunner.new(RoomTest, Test::Unit::UI::SILENT) }
70
+ end
71
+
72
+ x.report("Shoulda") do
73
+ n.times { Test::Unit::UI::Console::TestRunner.new(ShouldaRoomTest, Test::Unit::UI::SILENT) }
74
+ end
75
+ end
76
+
@@ -0,0 +1,85 @@
1
+ require 'riot'
2
+ require 'stringio'
3
+
4
+ context "any context" do
5
+ setup do
6
+ @reporter = Riot::NilReport.new
7
+ @context = Riot::Context.new("a", @reporter)
8
+ end
9
+
10
+ context "that doesn't have passing tests" do
11
+ setup do
12
+ @context.should("a") { true }
13
+ @context.should("b") { false }
14
+ @context.should("c") { raise Exception, "blah" }
15
+ @context.report
16
+ end
17
+
18
+ asserts("passed test count") { @reporter.passes }.equals(1)
19
+ asserts("failure count") { @reporter.failures }.equals(1)
20
+ asserts("unexpected errors count") { @reporter.errors }.equals(1)
21
+ end # that doesn't have passing tests
22
+
23
+ context "when running setup:" do
24
+ setup { @context.setup { "foo" } }
25
+
26
+ asserts "topic becomes available to test as result of setup" do
27
+ @context.should("bar") { topic }.actual
28
+ end.equals("foo")
29
+ end # when running setup
30
+ end # any context
31
+
32
+ #
33
+ # Test Context
34
+
35
+ test_context = context("foo", Riot::NilReport.new) do
36
+ setup { @test_counter = 0 }
37
+ asserts("truthiness") { @test_counter += 1; true }
38
+ asserts("more truthiness") { @test_counter += 1; true }
39
+ end # A CONTEXT THAT IS DEQUEUED
40
+
41
+ context "test context" do
42
+ setup { Riot.dequeue_context(test_context) }
43
+ should("confirm context description") { test_context.to_s }.equals("foo")
44
+ should("confirm assertion count") { test_context.assertions.length }.equals(2)
45
+
46
+ should("call setup once per context") do
47
+ test_context.situation.instance_variable_get(:@test_counter) # yuck
48
+ end.equals(2)
49
+ end # test context
50
+
51
+ #
52
+ # Nested Context
53
+
54
+ inner_nested_context, other_nested_context = nil, nil
55
+ nested_context = context("foo", Riot::NilReport.new) do
56
+ setup do
57
+ @test_counter = 0
58
+ @foo = "bar"
59
+ end
60
+ asserts("truthiness") { @test_counter += 1; true }
61
+
62
+ inner_nested_context = context("baz") do
63
+ setup { @test_counter += 10 }
64
+ end # A CONTEXT THAT IS DEQUEUED
65
+
66
+ other_nested_context = context("bum") {} # A CONTEXT THAT IS DEQUEUED
67
+ end # A CONTEXT THAT IS DEQUEUED
68
+
69
+ context "nested context" do
70
+ setup do
71
+ [nested_context, inner_nested_context, other_nested_context].each do |c|
72
+ Riot.dequeue_context(c)
73
+ end
74
+ end
75
+
76
+ should("inherit parent context") do
77
+ inner_nested_context.situation.instance_variable_get(:@test_counter)
78
+ end.equals(10)
79
+
80
+ should("chain context names") { inner_nested_context.to_s }.equals("foo baz")
81
+
82
+ asserts "parent setup is called even if setup not defined for self" do
83
+ other_nested_context.situation.instance_variable_get(:@foo)
84
+ end.equals("bar")
85
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: riot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Justin Knowlden
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-03 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: An extremely fast, expressive, and context-driven unit-testing framework. A replacement for all other testing frameworks
17
+ email:
18
+ - gus@gusg.us
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README.markdown
25
+ files:
26
+ - MIT-LICENSE
27
+ - README.markdown
28
+ - lib/riot.rb
29
+ - lib/riot/assertion.rb
30
+ - lib/riot/context.rb
31
+ - lib/riot/macros.rb
32
+ - lib/riot/report.rb
33
+ - riot.gemspec
34
+ has_rdoc: true
35
+ homepage: http://github.com/thumblemonks/protest
36
+ licenses: []
37
+
38
+ post_install_message:
39
+ rdoc_options:
40
+ - --main
41
+ - README.markdown
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.3.5
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: An extremely fast, expressive, and context-driven unit-testing framework
63
+ test_files:
64
+ - Rakefile
65
+ - test/assertion_test.rb
66
+ - test/benchmark/simple_context_and_assertions.rb
67
+ - test/context_test.rb