mcmire-protest 0.2.4

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 ADDED
@@ -0,0 +1,2 @@
1
+ doc
2
+ dist
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2009 Nicolas Sanguinetti
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,200 @@
1
+ = Protest, the simplicity rebel test framework
2
+
3
+ require "protest"
4
+
5
+ Protest.context("A user") do
6
+ setup do
7
+ @user = User.new(:name => "John Doe", :email => "john@example.org")
8
+ end
9
+
10
+ test "has a name" do
11
+ assert_equal "John Doe", @user.name
12
+ end
13
+
14
+ test "has an email" do
15
+ assert_equal "john@example.org", @user.email
16
+ end
17
+ end
18
+
19
+ Protest is a small, simple, and easy-to-extend testing framework for ruby. It
20
+ was written as a replacement for Test::Unit, given how awful its code is, and
21
+ how difficult it is to extend in order to add new features.
22
+
23
+ I believe in minimalistic software, which is easily understood, easy to test,
24
+ and specially, easy to extend for third parties. That's where I'm aiming with
25
+ Protest.
26
+
27
+ == Get it
28
+
29
+ gem install protest
30
+
31
+ Or
32
+
33
+ rip install git://github.com/foca/protest.git v0.2.3
34
+
35
+ == Setup and teardown
36
+
37
+ If you need to run code before or after each test, declare a +setup+ or
38
+ +teardown+ block (respectively.)
39
+
40
+ Protest.context("A user") do
41
+ setup do # this runs before each test
42
+ @user = User.create(:name => "John")
43
+ end
44
+
45
+ teardown do # this runs after each test
46
+ @user.destroy
47
+ end
48
+ end
49
+
50
+ +setup+ and +teardown+ blocks are evaluated in the same context as your test,
51
+ which means any instance variables defined in any of them are available in the
52
+ rest.
53
+
54
+ You can also use +global_setup+ and +global_teardown+ to run code only once per
55
+ test case. +global_setup+ blocks will run once before the first test is run, and
56
+ +global_teardown+ will run after all the tests have been run.
57
+
58
+ These methods, however, are dangerous, and should be used with caution, as
59
+ they might introduce dependencies between your tests if you don't write
60
+ your tests properly. Make sure that any state modified by code run in a
61
+ +global_setup+ or +global_teardown+ isn't changed in any of your tests.
62
+
63
+ Also, you should be aware that the code of +global_setup+ and +global_teardown+
64
+ blocks isn't evaluated in the same context as your tests and normal
65
+ +setup+/+teardown+ blocks are, so you can't share instance variables between
66
+ them.
67
+
68
+ == Nested contexts
69
+
70
+ Break down your test into logical chunks with nested contexts:
71
+
72
+ Protest.context("A user") do
73
+ setup do
74
+ @user = User.make
75
+ end
76
+
77
+ context "when validating" do
78
+ test "validates name" do
79
+ @user.name = nil
80
+ assert !@user.valid?
81
+ end
82
+
83
+ # etc, etc
84
+ end
85
+
86
+ context "doing something else" do
87
+ # your get the idea
88
+ end
89
+ end
90
+
91
+ Any +setup+ or +teardown+ blocks you defined in a context will run in that
92
+ context and in _any_ other context nested in it.
93
+
94
+ == Pending tests
95
+
96
+ There are two ways of marking a test as pending. You can declare a test with no
97
+ body:
98
+
99
+ Protest.context("Some tests") do
100
+ test "this test will be marked as pending"
101
+
102
+ test "this tests is also pending"
103
+
104
+ test "this test isn't pending" do
105
+ assert true
106
+ end
107
+ end
108
+
109
+ Or you can call the +pending+ method from inside your test.
110
+
111
+ Protest.context("Some tests") do
112
+ test "this test is pending" do
113
+ pending "oops, this doesn't work"
114
+ assert false
115
+ end
116
+ end
117
+
118
+ == Custom assertions
119
+
120
+ By default Protest bundles all the assertions defined in Test::Unit (it
121
+ literally requires them), so check its documentation for all the goodness.
122
+
123
+ If you want to add assertions, just define methods that rely on +assert+ or
124
+ +assert_block+. The former takes a boolean and an optional error message as
125
+ arguments, while the latter takes an optional error message as an argument and
126
+ a block. The assertions is considered to fail if the block evaluates to neither
127
+ +false+ nor +nil+.
128
+
129
+ For example:
130
+
131
+ module AwesomenessAssertions
132
+ def assert_awesomeness(object)
133
+ assert object.awesome?, "#{object.inspect} is not awesome enough"
134
+ end
135
+ end
136
+
137
+ class Protest::TestCase
138
+ include AwesomenessAssertions
139
+ end
140
+
141
+ You could also define rspec-like matchers if you like that style. See
142
+ <tt>matchers.rb</tt> in the examples directory for an example.
143
+
144
+ == Reports
145
+
146
+ Protest can report the output of a test suite in many ways. The library ships
147
+ with a <tt>:progress</tt> report, and a <tt>:documentation</tt> report,
148
+ <tt>:progress</tt> being the default.
149
+
150
+ === Progress report
151
+
152
+ This is the default option, but you can force this by calling
153
+ <tt>Protest.report_with(:progress)</tt>.
154
+
155
+ The progress report will output the "classic" Test::Unit output of periods for
156
+ passing tests, "F" for failing assertions, "E" for unrescued exceptions, and
157
+ "P" for pending tests, in full color.
158
+
159
+ === Documentation report
160
+
161
+ Use this report by calling <tt>Protest.report_with(:documentation)</tt>
162
+
163
+ For each testcase in your suite, this will output the description of the test
164
+ case (whatever you provide TestCase.context), followed by the name of each test
165
+ in that context, one per line. For example:
166
+
167
+ Protest.context "A user" do
168
+ test "has a name"
169
+ test "has an email"
170
+
171
+ context "validations" do
172
+ test "ensure the email can't be blank"
173
+ end
174
+ end
175
+
176
+ Will output, when run with the <tt>:documentation</tt> report:
177
+
178
+ A user
179
+ - has a name (Not Yet Implemented)
180
+ - has an email (Not Yet Implemented)
181
+
182
+ A user validations
183
+ - ensure the email can't be blank (Not Yet Implemented)
184
+
185
+ (The 'Not Yet Implemented' messages are because the tests have no body. See
186
+ "Pending tests", above.)
187
+
188
+ This is similar to the specdoc runner in rspec[http://rspec.info].
189
+
190
+ === Defining your own reports
191
+
192
+ This is really, really easy. All you need to do is subclass Report, and
193
+ register your subclass by calling +Protest.add_report+. See the
194
+ documentation for details, or take a look at the source code for
195
+ Protest::Reports::Progress and Protest::Reports::Documentation.
196
+
197
+ == Legal
198
+
199
+ Author:: Nicolás Sanguinetti — http://nicolassanguinetti.info
200
+ License:: MIT (see bundled LICENSE file for more info)
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ begin
2
+ require "hanna/rdoctask"
3
+ rescue LoadError
4
+ require "rake/rdoctask"
5
+ end
6
+
7
+ require "rake/testtask"
8
+
9
+ Rake::RDocTask.new do |rd|
10
+ rd.main = "README.rdoc"
11
+ rd.title = "API Documentation for Protest"
12
+ rd.rdoc_files.include("README.rdoc", "LICENSE", "lib/**/*.rb")
13
+ rd.rdoc_dir = "doc"
14
+ end
15
+
16
+ begin
17
+ require "mg"
18
+ MG.new("protest.gemspec")
19
+ rescue LoadError
20
+ end
21
+
22
+ Rake::TestTask.new
23
+
24
+ task :default => :test
@@ -0,0 +1,80 @@
1
+ require "protest"
2
+ require "test/unit/assertions"
3
+ require "action_controller/test_case"
4
+
5
+ begin
6
+ require "webrat"
7
+ rescue LoadError
8
+ $no_webrat = true
9
+ end
10
+
11
+ module Protest
12
+ module Rails
13
+ # Exclude rails' files from the errors
14
+ class BacktraceFilter < Utils::BacktraceFilter
15
+ include ::Rails::BacktraceFilterForTestUnit
16
+
17
+ def filter_backtrace(backtrace, prefix=nil)
18
+ super(backtrace, prefix).reject do |line|
19
+ line.starts_with?("/")
20
+ end
21
+ end
22
+ end
23
+
24
+ # Wrap all tests in a database transaction.
25
+ #
26
+ # TODO: make this optional somehow (yet enabled by default) so users of
27
+ # other ORMs don't run into problems.
28
+ module TransactionalTests
29
+ def run(*args, &block)
30
+ ActiveRecord::Base.connection.transaction do
31
+ super(*args, &block)
32
+ raise ActiveRecord::Rollback
33
+ end
34
+ end
35
+ end
36
+
37
+ # You should inherit from this TestCase in order to get rails' helpers
38
+ # loaded into Protest. These include all the assertions bundled with rails
39
+ # and your tests being wrapped in a transaction.
40
+ class TestCase < ::Protest::TestCase
41
+ include ::Test::Unit::Assertions
42
+ include ActiveSupport::Testing::Assertions
43
+ include TransactionalTests
44
+ end
45
+
46
+ class RequestTest < TestCase #:nodoc:
47
+ %w(response selector tag dom routing model).each do |kind|
48
+ include ActionController::Assertions.const_get("#{kind.camelize}Assertions")
49
+ end
50
+ end
51
+
52
+ # Make your integration tests inherit from this class, which bundles the
53
+ # integration runner included with rails, and webrat's test methods. You
54
+ # should use webrat for integration tests. Really.
55
+ class IntegrationTest < RequestTest
56
+ include ActionController::Integration::Runner
57
+ include Webrat::Methods unless $no_webrat
58
+ end
59
+ end
60
+
61
+ # The preferred way to declare a context (top level) is to use
62
+ # +Protest.describe+ or +Protest.context+, which will ensure you're using
63
+ # rails adapter with the helpers you need.
64
+ def self.context(description, &block)
65
+ Rails::TestCase.context(description, &block)
66
+ end
67
+
68
+ # Use +Protest.story+ to declare an integration test for your rails app. Note
69
+ # that the files should still be called 'test/integration/foo_test.rb' if you
70
+ # want the 'test:integration' rake task to pick them up.
71
+ def self.story(description, &block)
72
+ Rails::IntegrationTest.story(description, &block)
73
+ end
74
+
75
+ class << self
76
+ alias_method :describe, :context
77
+ end
78
+
79
+ self.backtrace_filter = Rails::BacktraceFilter.new
80
+ end
@@ -0,0 +1,121 @@
1
+ module Protest
2
+ class Report
3
+ # Define an event handler for your report. The different events fired in a
4
+ # report's life cycle are:
5
+ #
6
+ # :start:: Fired by the runner when starting the whole test suite.
7
+ # :enter:: Fired by the runner when starting a particular test case. It
8
+ # will get the test case as an argument.
9
+ # :test:: Fired by a test before it starts running. It will get the
10
+ # instance of TestCase for the given test as an argument.
11
+ # :assertion:: Fired by a test each time an assertion is run.
12
+ # :pass:: Fired by a test after it runs successfully without errors.
13
+ # It will get an instance of PassedTest as an argument.
14
+ # :pending:: Fired by a test which doesn't provide a test block or which
15
+ # calls TestCase#pending. It will get an instance of
16
+ # PendingTest as an argument.
17
+ # :failure:: Fired by a test in which an assertion failed. It will get an
18
+ # instance of FailedTest as an argument.
19
+ # :error:: Fired by a test where an uncaught exception was found. It
20
+ # will get an instance of ErroredTest as an argument.
21
+ # :exit:: Fired by the runner each time a test case finishes. It will
22
+ # take the test case as an argument.
23
+ # :end:: Fired by the runner at the end of the whole test suite.
24
+ #
25
+ # The event handler will receive the report as a first argument, plus any
26
+ # arguments documented above (depending on the event). It will also ensure
27
+ # that any handler for the same event declared on an ancestor class is run.
28
+ def self.on(event, &block)
29
+ define_method(:"on_#{event}") do |*args|
30
+ begin
31
+ super(*args)
32
+ rescue NoMethodError
33
+ end
34
+
35
+ block.call(self, *args)
36
+ end
37
+ end
38
+
39
+ on :test do |report, test|
40
+ report.tests << test
41
+ end
42
+
43
+ on :start do |report|
44
+ report.instance_eval { @started_at = Time.now }
45
+ end
46
+
47
+ on :pass do |report, passed_test|
48
+ report.passes << passed_test
49
+ end
50
+
51
+ on :pending do |report, pending_test|
52
+ report.pendings << pending_test
53
+ end
54
+
55
+ on :failure do |report, failed_test|
56
+ report.failures << failed_test
57
+ report.failures_and_errors << failed_test
58
+ end
59
+
60
+ on :error do |report, errored_test|
61
+ report.errors << errored_test
62
+ report.failures_and_errors << errored_test
63
+ end
64
+
65
+ on :assertion do |report|
66
+ report.add_assertion
67
+ end
68
+
69
+ # List all the tests (as PendingTest instances) that were pending.
70
+ def pendings
71
+ @pendings ||= []
72
+ end
73
+
74
+ # List all the tests (as PassedTest instances) that passed.
75
+ def passes
76
+ @passes ||= []
77
+ end
78
+
79
+ # List all the tests (as FailedTest instances) that failed an assertion.
80
+ def failures
81
+ @failures ||= []
82
+ end
83
+
84
+ # List all the tests (as ErroredTest instances) that raised an unrescued
85
+ # exception.
86
+ def errors
87
+ @errors ||= []
88
+ end
89
+
90
+ # Aggregated and ordered list of tests that either failed an assertion or
91
+ # raised an unrescued exception. Useful for displaying back to the user.
92
+ def failures_and_errors
93
+ @failures_and_errors ||= []
94
+ end
95
+
96
+ # Log an assertion was run (whether it succeeded or failed.)
97
+ def add_assertion
98
+ @assertions ||= 0
99
+ @assertions += 1
100
+ end
101
+
102
+ # Number of assertions run during the report.
103
+ def assertions
104
+ @assertions || 0
105
+ end
106
+
107
+ def tests
108
+ @tests ||= []
109
+ end
110
+
111
+ # Amount ot tests run (whether passed, pending, failed, or errored.)
112
+ def total_tests
113
+ tests.size
114
+ end
115
+
116
+ # Seconds taken since the test suite started running
117
+ def time_elapsed
118
+ Time.now - @started_at
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,77 @@
1
+ module Protest
2
+ # For each testcase in your suite, this will output the description of the test
3
+ # case (whatever you provide TestCase.context), followed by the name of each test
4
+ # in that context, one per line. For example:
5
+ #
6
+ # Protest.context "A user" do
7
+ # test "has a name" do
8
+ # ...
9
+ # end
10
+ #
11
+ # test "has an email" do
12
+ # ...
13
+ # end
14
+ #
15
+ # context "validations" do
16
+ # test "ensure the email can't be blank" do
17
+ # ...
18
+ # end
19
+ # end
20
+ # end
21
+ #
22
+ # Will output, when run with the +:documentation+ report:
23
+ #
24
+ # A user
25
+ # - has a name
26
+ # - has an email
27
+ #
28
+ # A user validations
29
+ # - ensure the email can't be blank
30
+ #
31
+ # This is based on the specdoc runner in rspec[http://rspec.info].
32
+ class Reports::Documentation < Report
33
+ include Utils::Summaries
34
+ include Utils::ColorfulOutput
35
+
36
+ attr_reader :stream #:nodoc:
37
+
38
+ # Set the stream where the report will be written to. STDOUT by default.
39
+ def initialize(stream=STDOUT)
40
+ @stream = stream
41
+ end
42
+
43
+ on :enter do |report, context|
44
+ report.puts context.description unless context.tests.empty?
45
+ end
46
+
47
+ on :pass do |report, passed_test|
48
+ report.puts "- #{passed_test.test_name}", :passed
49
+ end
50
+
51
+ on :failure do |report, failed_test|
52
+ position = report.failures_and_errors.index(failed_test) + 1
53
+ report.puts "- #{failed_test.test_name} (#{position})", :failed
54
+ end
55
+
56
+ on :error do |report, errored_test|
57
+ position = report.failures_and_errors.index(errored_test) + 1
58
+ report.puts "- #{errored_test.test_name} (#{position})", :errored
59
+ end
60
+
61
+ on :pending do |report, pending_test|
62
+ report.puts "- #{pending_test.test_name} (#{pending_test.pending_message})", :pending
63
+ end
64
+
65
+ on :exit do |report, context|
66
+ report.puts unless context.tests.empty?
67
+ end
68
+
69
+ on :end do |report|
70
+ report.summarize_pending_tests
71
+ report.summarize_errors
72
+ report.summarize_test_totals
73
+ end
74
+ end
75
+
76
+ add_report :documentation, Reports::Documentation
77
+ end
@@ -0,0 +1,46 @@
1
+ module Protest
2
+ # The +:progress+ report will output a +.+ for each passed test in the suite,
3
+ # a +P+ for each pending test, an +F+ for each test that failed an assertion,
4
+ # and an +E+ for each test that raised an unrescued exception.
5
+ #
6
+ # At the end of the suite it will output a list of all pending tests, with
7
+ # files and line numbers, and after that a list of all failures and errors,
8
+ # which also contains the first 3 lines of the backtrace for each.
9
+ class Reports::Progress < Report
10
+ include Utils::Summaries
11
+ include Utils::ColorfulOutput
12
+
13
+ attr_reader :stream #:nodoc:
14
+
15
+ # Set the stream where the report will be written to. STDOUT by default.
16
+ def initialize(stream=STDOUT)
17
+ @stream = stream
18
+ end
19
+
20
+ on :end do |report|
21
+ report.puts
22
+ report.puts
23
+ report.summarize_pending_tests
24
+ report.summarize_errors
25
+ report.summarize_test_totals
26
+ end
27
+
28
+ on :pass do |report, pass|
29
+ report.print ".", :passed
30
+ end
31
+
32
+ on :pending do |report, pending|
33
+ report.print "P", :pending
34
+ end
35
+
36
+ on :failure do |report, failure|
37
+ report.print "F", :failed
38
+ end
39
+
40
+ on :error do |report, error|
41
+ report.print "E", :errored
42
+ end
43
+ end
44
+
45
+ add_report :progress, Reports::Progress
46
+ end
@@ -0,0 +1,2 @@
1
+ module Protest::Reports # :nodoc:
2
+ end
@@ -0,0 +1,39 @@
1
+ module Protest
2
+ class Runner
3
+ # Set up the test runner. Takes in a report that will be passed to test
4
+ # cases for reporting.
5
+ def initialize(report)
6
+ @report = report
7
+ end
8
+
9
+ # Run a set of test cases, provided as arguments. This will fire relevant
10
+ # events on the runner's report, at the +start+ and +end+ of the test run,
11
+ # and before and after each test case (+enter+ and +exit+.)
12
+ def run(*test_cases)
13
+ @report.on_start if @report.respond_to?(:on_start)
14
+ test_cases.each do |test_case|
15
+ @report.on_enter(test_case) if @report.respond_to?(:on_enter)
16
+ test_case.run(self)
17
+ @report.on_exit(test_case) if @report.respond_to?(:on_exit)
18
+ end
19
+ @report.on_end if @report.respond_to?(:on_end)
20
+ end
21
+
22
+ # Run a test and report if it passes, fails, or is pending. Takes the name
23
+ # of the test as an argument. By passing +true+ as the second argument, you
24
+ # force any exceptions to be re-raied and the test not reported as a pass
25
+ # after it finishes (for global setup/teardown blocks)
26
+ def report(test, running_global_setup_or_teardown=false)
27
+ @report.on_test(Test.new(test)) if @report.respond_to?(:on_test) && !running_global_setup_or_teardown
28
+ test.run(@report)
29
+ @report.on_pass(PassedTest.new(test)) unless running_global_setup_or_teardown
30
+ rescue Pending => e
31
+ @report.on_pending(PendingTest.new(test, e))
32
+ rescue AssertionFailed => e
33
+ @report.on_failure(FailedTest.new(test, e))
34
+ rescue Exception => e
35
+ @report.on_error(ErroredTest.new(test, e))
36
+ raise if running_global_setup_or_teardown
37
+ end
38
+ end
39
+ end