mcmire-protest 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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