protest 0.2
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 +2 -0
- data/LICENSE +22 -0
- data/README.rdoc +190 -0
- data/Rakefile +24 -0
- data/lib/protest.rb +94 -0
- data/lib/protest/rails.rb +62 -0
- data/lib/protest/report.rb +113 -0
- data/lib/protest/reports.rb +2 -0
- data/lib/protest/reports/documentation.rb +77 -0
- data/lib/protest/reports/progress.rb +46 -0
- data/lib/protest/runner.rb +42 -0
- data/lib/protest/test_case.rb +221 -0
- data/lib/protest/tests.rb +90 -0
- data/lib/protest/utils.rb +6 -0
- data/lib/protest/utils/backtrace_filter.rb +19 -0
- data/lib/protest/utils/colorful_output.rb +67 -0
- data/lib/protest/utils/summaries.rb +116 -0
- data/protest.gemspec +38 -0
- metadata +72 -0
data/.gitignore
ADDED
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,190 @@
|
|
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 @user.name == "John Doe"
|
12
|
+
end
|
13
|
+
|
14
|
+
test "has an email" do
|
15
|
+
assert @user.email == "john@example.org"
|
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
|
34
|
+
|
35
|
+
== Assertions
|
36
|
+
|
37
|
+
You probably wonder why the above example doesn't have any assertion other than
|
38
|
+
+assert+. Where's +assert_equal+, right? Well, the idea is to keep it slim. If
|
39
|
+
you want to use more assertions, you're free to define your own. You can also
|
40
|
+
use all the assertions provided by Test::Unit, by just including them:
|
41
|
+
|
42
|
+
require "test/unit/assertions"
|
43
|
+
|
44
|
+
class Protest::TestCase
|
45
|
+
include Test::Unit::Assertions
|
46
|
+
end
|
47
|
+
|
48
|
+
You could also define rspec-like matchers if you like that. See +matchers.rb+ in
|
49
|
+
the examples directory for an example.
|
50
|
+
|
51
|
+
== Setup and teardown
|
52
|
+
|
53
|
+
If you need to run code before or after each test, declare a +setup+ or
|
54
|
+
+teardown+ block (respectively.)
|
55
|
+
|
56
|
+
Protest.context("A user") do
|
57
|
+
setup do # this runs before each test
|
58
|
+
@user = User.create(:name => "John")
|
59
|
+
end
|
60
|
+
|
61
|
+
teardown do # this runs after each test
|
62
|
+
@user.destroy
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
+setup+ and +teardown+ blocks are evaluated in the same context as your test,
|
67
|
+
which means any instance variables defined in any of them are available in the
|
68
|
+
rest.
|
69
|
+
|
70
|
+
You can also use +global_setup+ and +global_teardown+ to run code only once per
|
71
|
+
test case. +global_setup+ blocks will run once before the first test is run, and
|
72
|
+
+global_teardown+ will run after all the tests have been run.
|
73
|
+
|
74
|
+
These methods, however, are dangerous, and should be used with caution, as
|
75
|
+
they might introduce dependencies between your tests if you don't write
|
76
|
+
your tests properly. Make sure that any state modified by code run in a
|
77
|
+
+global_setup+ or +global_teardown+ isn't changed in any of your tests.
|
78
|
+
|
79
|
+
Also, you should be aware that the code of +global_setup+ and +global_teardown+
|
80
|
+
blocks isn't evaluated in the same context as your tests and normal
|
81
|
+
+setup+/+teardown+ blocks are, so you can't share instance variables between
|
82
|
+
them.
|
83
|
+
|
84
|
+
== Nested contexts
|
85
|
+
|
86
|
+
Break down your test into logical chunks with nested contexts:
|
87
|
+
|
88
|
+
Protest.context("A user") do
|
89
|
+
setup do
|
90
|
+
@user = User.make
|
91
|
+
end
|
92
|
+
|
93
|
+
context "when validating" do
|
94
|
+
test "validates name" do
|
95
|
+
@user.name = nil
|
96
|
+
assert !@user.valid?
|
97
|
+
end
|
98
|
+
|
99
|
+
# etc, etc
|
100
|
+
end
|
101
|
+
|
102
|
+
context "doing something else" do
|
103
|
+
# your get the idea
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
Any +setup+ or +teardown+ blocks you defined in a context will run in that
|
108
|
+
context and in _any_ other context nested in it.
|
109
|
+
|
110
|
+
== Pending tests
|
111
|
+
|
112
|
+
There are two ways of marking a test as pending. You can declare a test with no
|
113
|
+
body:
|
114
|
+
|
115
|
+
Protest.context("Some tests") do
|
116
|
+
test "this test will be marked as pending"
|
117
|
+
|
118
|
+
test "this tests is also pending"
|
119
|
+
|
120
|
+
test "this test isn't pending" do
|
121
|
+
assert true
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
Or you can call the +pending+ method from inside your test.
|
126
|
+
|
127
|
+
Protest.context("Some tests") do
|
128
|
+
test "this test is pending" do
|
129
|
+
pending "oops, this doesn't work"
|
130
|
+
assert false
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
== Reports
|
135
|
+
|
136
|
+
Protest can report the output of a test suite in many ways. The library ships
|
137
|
+
with a +:progress+ report, and a +:documentation+ report, +:progress+ being the
|
138
|
+
default.
|
139
|
+
|
140
|
+
=== Progress report
|
141
|
+
|
142
|
+
Use this report by calling <tt>Protest.report_with(:progress)</tt> (this isn't
|
143
|
+
needed though, since this is the default report.)
|
144
|
+
|
145
|
+
The progress report will output the "classic" Test::Unit output of periods for
|
146
|
+
passing tests, "F" for failing assertions, "E" for unrescued exceptions, and
|
147
|
+
"P" for pending tests, in full color.
|
148
|
+
|
149
|
+
=== Documentation report
|
150
|
+
|
151
|
+
Use this report by calling <tt>Protest.report_with(:documentation)</tt>
|
152
|
+
|
153
|
+
For each testcase in your suite, this will output the description of the test
|
154
|
+
case (whatever you provide TestCase.context), followed by the name of each test
|
155
|
+
in that context, one per line. For example:
|
156
|
+
|
157
|
+
Protest.context "A user" do
|
158
|
+
test "has a name"
|
159
|
+
test "has an email"
|
160
|
+
|
161
|
+
context "validations" do
|
162
|
+
test "ensure the email can't be blank"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
Will output, when run with the +:documentation+ report:
|
167
|
+
|
168
|
+
A user
|
169
|
+
- has a name (Not Yet Implemented)
|
170
|
+
- has an email (Not Yet Implemented)
|
171
|
+
|
172
|
+
A user validations
|
173
|
+
- ensure the email can't be blank (Not Yet Implemented)
|
174
|
+
|
175
|
+
(The 'Not Yet Implemented' messages are because the tests have no body. See
|
176
|
+
"Pending tests", above.)
|
177
|
+
|
178
|
+
This is similar to the specdoc runner in rspec[http://rspec.info].
|
179
|
+
|
180
|
+
=== Defining your own reports
|
181
|
+
|
182
|
+
This is really, really easy. All you need to do is subclass Report, and
|
183
|
+
register your subclass by calling +Protest.add_report+. See the
|
184
|
+
documentation for details, or take a look at the source code for
|
185
|
+
Protest::Reports::Progress and Protest::Reports::Documentation.
|
186
|
+
|
187
|
+
== Legal
|
188
|
+
|
189
|
+
Author:: Nicolás Sanguinetti — http://nicolassanguinetti.info
|
190
|
+
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
|
data/lib/protest.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
module Protest
|
2
|
+
# Exception raised when an assertion fails. See TestCase#assert
|
3
|
+
class AssertionFailed < StandardError; end
|
4
|
+
|
5
|
+
# Exception raised to mark a test as pending. See TestCase#pending
|
6
|
+
class Pending < StandardError; end
|
7
|
+
|
8
|
+
# Register a new Report. This will make your report available to Protest,
|
9
|
+
# allowing you to run your tests through this report. For example
|
10
|
+
#
|
11
|
+
# module Protest
|
12
|
+
# class Reports::MyAwesomeReport < Report
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# add_report :awesomesauce, MyAwesomeReport
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# See Protest.report_with to see how to select which report will be used.
|
19
|
+
def self.add_report(name, report)
|
20
|
+
available_reports[name] = report
|
21
|
+
end
|
22
|
+
|
23
|
+
# Register a test case to be run with Protest. This is done automatically
|
24
|
+
# whenever you subclass Protest::TestCase, so you probably shouldn't pay
|
25
|
+
# much attention to this method.
|
26
|
+
def self.add_test_case(test_case)
|
27
|
+
available_test_cases << test_case
|
28
|
+
end
|
29
|
+
|
30
|
+
# Set to +false+ to avoid running tests +at_exit+. Default is +true+.
|
31
|
+
def self.autorun=(flag)
|
32
|
+
@autorun = flag
|
33
|
+
end
|
34
|
+
|
35
|
+
# Checks to see if tests should be run +at_exit+ or not. Default is +true+.
|
36
|
+
# See Protest.autorun=
|
37
|
+
def self.autorun?
|
38
|
+
!!@autorun
|
39
|
+
end
|
40
|
+
|
41
|
+
# Run all registered test cases through the selected report. You can pass
|
42
|
+
# arguments to the Report constructor here.
|
43
|
+
#
|
44
|
+
# See Protest.add_test_case and Protest.report_with
|
45
|
+
def self.run_all_tests!(*report_args)
|
46
|
+
Runner.new(@report).run(*available_test_cases)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Select the name of the Report to use when running tests. See
|
50
|
+
# Protest.add_report for more information on registering a report.
|
51
|
+
#
|
52
|
+
# Any extra arguments will be forwarded to the report's #initialize method.
|
53
|
+
#
|
54
|
+
# The default report is Protest::Reports::Progress
|
55
|
+
def self.report_with(name, *report_args)
|
56
|
+
@report = report(name, *report_args)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Load a report by name, initializing it with the extra arguments provided.
|
60
|
+
# If the given +name+ doesn't match a report registered via
|
61
|
+
# Protest.add_report then the method will raise IndexError.
|
62
|
+
def self.report(name, *report_args)
|
63
|
+
available_reports.fetch(name).new(*report_args)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.available_test_cases
|
67
|
+
@test_cases ||= []
|
68
|
+
end
|
69
|
+
private_class_method :available_test_cases
|
70
|
+
|
71
|
+
def self.available_reports
|
72
|
+
@available_reports ||= {}
|
73
|
+
end
|
74
|
+
private_class_method :available_reports
|
75
|
+
end
|
76
|
+
|
77
|
+
require "protest/utils"
|
78
|
+
require "protest/utils/backtrace_filter"
|
79
|
+
require "protest/utils/summaries"
|
80
|
+
require "protest/utils/colorful_output"
|
81
|
+
require "protest/test_case"
|
82
|
+
require "protest/tests"
|
83
|
+
require "protest/runner"
|
84
|
+
require "protest/report"
|
85
|
+
require "protest/reports"
|
86
|
+
require "protest/reports/progress"
|
87
|
+
require "protest/reports/documentation"
|
88
|
+
|
89
|
+
Protest.autorun = true
|
90
|
+
Protest.report_with(:progress)
|
91
|
+
|
92
|
+
at_exit do
|
93
|
+
Protest.run_all_tests! if Protest.autorun?
|
94
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "protest"
|
2
|
+
require "test/unit/assertions"
|
3
|
+
require "action_controller/test_case"
|
4
|
+
require "webrat"
|
5
|
+
|
6
|
+
module Protest
|
7
|
+
module Rails
|
8
|
+
# Wrap all tests in a database transaction.
|
9
|
+
#
|
10
|
+
# TODO: make this optional somehow (yet enabled by default) so users of
|
11
|
+
# other ORMs don't run into problems.
|
12
|
+
module TransactionalTests
|
13
|
+
def run(*args, &block)
|
14
|
+
ActiveRecord::Base.connection.transaction do
|
15
|
+
super(*args, &block)
|
16
|
+
raise ActiveRecord::Rollback
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# You should inherit from this TestCase in order to get rails' helpers
|
22
|
+
# loaded into Protest. These include all the assertions bundled with rails
|
23
|
+
# and your tests being wrapped in a transaction.
|
24
|
+
class TestCase < ::Protest::TestCase
|
25
|
+
include ::Test::Unit::Assertions
|
26
|
+
include ActiveSupport::Testing::Assertions
|
27
|
+
include TransactionalTests
|
28
|
+
end
|
29
|
+
|
30
|
+
class RequestTest < TestCase #:nodoc:
|
31
|
+
%w(response selector tag dom routing model).each do |kind|
|
32
|
+
include ActionController::Assertions.const_get("#{kind.camelize}Assertions")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Make your integration tests inherit from this class, which bundles the
|
37
|
+
# integration runner included with rails, and webrat's test methods. You
|
38
|
+
# should use webrat for integration tests. Really.
|
39
|
+
class IntegrationTest < RequestTest
|
40
|
+
include ActionController::Integration::Runner
|
41
|
+
include Webrat::Methods
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# The preferred way to declare a context (top level) is to use
|
46
|
+
# +Protest.describe+ or +Protest.context+, which will ensure you're using
|
47
|
+
# rails adapter with the helpers you need.
|
48
|
+
def self.context(description, &block)
|
49
|
+
Rails::TestCase.context(description, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Use +Protest.story+ to declare an integration test for your rails app. Note
|
53
|
+
# that the files should still be called 'test/integration/foo_test.rb' if you
|
54
|
+
# want the 'test:integration' rake task to pick them up.
|
55
|
+
def self.story(description, &block)
|
56
|
+
Rails::IntegrationTest.story(description, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
class << self
|
60
|
+
alias_method :describe, :context
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,113 @@
|
|
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 :start do |report|
|
40
|
+
report.instance_eval { @started_at = Time.now }
|
41
|
+
end
|
42
|
+
|
43
|
+
on :pass do |report, passed_test|
|
44
|
+
report.passes << passed_test
|
45
|
+
end
|
46
|
+
|
47
|
+
on :pending do |report, pending_test|
|
48
|
+
report.pendings << pending_test
|
49
|
+
end
|
50
|
+
|
51
|
+
on :failure do |report, failed_test|
|
52
|
+
report.failures << failed_test
|
53
|
+
report.failures_and_errors << failed_test
|
54
|
+
end
|
55
|
+
|
56
|
+
on :error do |report, errored_test|
|
57
|
+
report.errors << errored_test
|
58
|
+
report.failures_and_errors << errored_test
|
59
|
+
end
|
60
|
+
|
61
|
+
on :assertion do |report|
|
62
|
+
report.add_assertion
|
63
|
+
end
|
64
|
+
|
65
|
+
# List all the tests (as PendingTest instances) that were pending.
|
66
|
+
def pendings
|
67
|
+
@pendings ||= []
|
68
|
+
end
|
69
|
+
|
70
|
+
# List all the tests (as PassedTest instances) that passed.
|
71
|
+
def passes
|
72
|
+
@passes ||= []
|
73
|
+
end
|
74
|
+
|
75
|
+
# List all the tests (as FailedTest instances) that failed an assertion.
|
76
|
+
def failures
|
77
|
+
@failures ||= []
|
78
|
+
end
|
79
|
+
|
80
|
+
# List all the tests (as ErroredTest instances) that raised an unrescued
|
81
|
+
# exception.
|
82
|
+
def errors
|
83
|
+
@errors ||= []
|
84
|
+
end
|
85
|
+
|
86
|
+
# Aggregated and ordered list of tests that either failed an assertion or
|
87
|
+
# raised an unrescued exception. Useful for displaying back to the user.
|
88
|
+
def failures_and_errors
|
89
|
+
@failures_and_errors ||= []
|
90
|
+
end
|
91
|
+
|
92
|
+
# Log an assertion was run (whether it succeeded or failed.)
|
93
|
+
def add_assertion
|
94
|
+
@assertions ||= 0
|
95
|
+
@assertions += 1
|
96
|
+
end
|
97
|
+
|
98
|
+
# Number of assertions run during the report.
|
99
|
+
def assertions
|
100
|
+
@assertions || 0
|
101
|
+
end
|
102
|
+
|
103
|
+
# Amount ot tests run (whether passed, pending, failed, or errored.)
|
104
|
+
def total_tests
|
105
|
+
passes.size + failures.size + errors.size + pendings.size
|
106
|
+
end
|
107
|
+
|
108
|
+
# Seconds taken since the test suite started running
|
109
|
+
def time_elapsed
|
110
|
+
Time.now - @started_at
|
111
|
+
end
|
112
|
+
end
|
113
|
+
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,42 @@
|
|
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. You can avoid reporting a passed test by
|
24
|
+
# passing +false+ as a second argument.
|
25
|
+
def report(test, report_success=true)
|
26
|
+
@report.on_test(Test.new(test)) if @report.respond_to?(:on_test)
|
27
|
+
yield
|
28
|
+
@report.on_pass(PassedTest.new(test)) if report_success
|
29
|
+
rescue Pending => e
|
30
|
+
@report.on_pending(PendingTest.new(test, e))
|
31
|
+
rescue AssertionFailed => e
|
32
|
+
@report.on_failure(FailedTest.new(test, e))
|
33
|
+
rescue Exception => e
|
34
|
+
@report.on_error(ErroredTest.new(test, e))
|
35
|
+
end
|
36
|
+
|
37
|
+
def assert(condition, message) #:nodoc:
|
38
|
+
@report.add_assertion
|
39
|
+
raise AssertionFailed, message unless condition
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
module Protest
|
2
|
+
# Define a top level test context where to define tests. This works exactly
|
3
|
+
# the same as subclassing TestCase explicitly.
|
4
|
+
#
|
5
|
+
# Protest.context "A user" do
|
6
|
+
# ...
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# is just syntax sugar to write:
|
10
|
+
#
|
11
|
+
# class TestUser < Protest::TestCase
|
12
|
+
# self.description = "A user"
|
13
|
+
# ...
|
14
|
+
# end
|
15
|
+
def self.context(description, &block)
|
16
|
+
TestCase.context(description, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
alias_method :describe, :context
|
21
|
+
alias_method :story, :context
|
22
|
+
end
|
23
|
+
|
24
|
+
# A TestCase defines a suite of related tests. You can further categorize
|
25
|
+
# your tests by declaring nested contexts inside the class. See
|
26
|
+
# TestCase.context.
|
27
|
+
class TestCase
|
28
|
+
# Run all tests in this context. Takes a Report instance in order to
|
29
|
+
# provide output.
|
30
|
+
def self.run(result)
|
31
|
+
result.report("#{description} global setup", false) { do_global_setup }
|
32
|
+
tests.each {|test| test.run(result) }
|
33
|
+
result.report("#{description} global teardown", false) { do_global_teardown }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Tests added to this context.
|
37
|
+
def self.tests
|
38
|
+
@tests ||= []
|
39
|
+
end
|
40
|
+
|
41
|
+
# Add a test to be run in this context. This method is aliased as +it+ and
|
42
|
+
# +should+ for your comfort.
|
43
|
+
def self.test(name, &block)
|
44
|
+
tests << new(name, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Add a setup block to be run before each test in this context. This method
|
48
|
+
# is aliased as +before+ for your comfort.
|
49
|
+
def self.setup(&block)
|
50
|
+
define_method :setup do
|
51
|
+
super
|
52
|
+
instance_eval(&block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Add a +setup+ block that will be run *once* for the entire test case,
|
57
|
+
# before the first test is run.
|
58
|
+
#
|
59
|
+
# Keep in mind that while +setup+ blocks are evaluated on the context of the
|
60
|
+
# test, and thus you can share state between them, your tests will not be
|
61
|
+
# able to access instance variables set in a +global_setup+ block.
|
62
|
+
#
|
63
|
+
# This is usually not needed (and generally using it is a code smell, since
|
64
|
+
# you could make a test dependent on the state of other tests, which is a
|
65
|
+
# huge problem), but it comes in handy when you need to do expensive
|
66
|
+
# operations in your test setup/teardown and the tests won't modify the
|
67
|
+
# state set on this operations. For example, creating large amount of
|
68
|
+
# records in a database or filesystem, when your tests will only read these
|
69
|
+
# records.
|
70
|
+
def self.global_setup(&block)
|
71
|
+
(class << self; self; end).class_eval do
|
72
|
+
define_method :do_global_setup do
|
73
|
+
super
|
74
|
+
instance_eval(&block)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Add a teardown block to be run after each test in this context. This
|
80
|
+
# method is aliased as +after+ for your comfort.
|
81
|
+
def self.teardown(&block)
|
82
|
+
define_method :teardown do
|
83
|
+
instance_eval(&block)
|
84
|
+
super
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Add a +teardown+ block that will be run *once* for the entire test case,
|
89
|
+
# after the last test is run.
|
90
|
+
#
|
91
|
+
# Keep in mind that while +teardown+ blocks are evaluated on the context of
|
92
|
+
# the test, and thus you can share state between the tests and the
|
93
|
+
# teardown blocks, you will not be able to access instance variables set in
|
94
|
+
# a test from your +global_teardown+ block.
|
95
|
+
#
|
96
|
+
# See TestCase.global_setup for a discussion on why these methods are best
|
97
|
+
# avoided unless you really need them and use them carefully.
|
98
|
+
def self.global_teardown(&block)
|
99
|
+
(class << self; self; end).class_eval do
|
100
|
+
define_method :do_global_teardown do
|
101
|
+
instance_eval(&block)
|
102
|
+
super
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
# Define a new test context nested under the current one. All +setup+ and
|
109
|
+
# +teardown+ blocks defined on the current context will be inherited by the
|
110
|
+
# new context. This method is aliased as +describe+ for your comfort.
|
111
|
+
def self.context(description, &block)
|
112
|
+
subclass = Class.new(self)
|
113
|
+
subclass.class_eval(&block) if block
|
114
|
+
subclass.description = description
|
115
|
+
const_set(sanitize_description(description), subclass)
|
116
|
+
end
|
117
|
+
|
118
|
+
class << self
|
119
|
+
# Fancy name for your test case, reports can use this to give nice,
|
120
|
+
# descriptive output when running your tests.
|
121
|
+
attr_accessor :description
|
122
|
+
|
123
|
+
alias_method :describe, :context
|
124
|
+
alias_method :story, :context
|
125
|
+
|
126
|
+
alias_method :before, :setup
|
127
|
+
alias_method :after, :teardown
|
128
|
+
|
129
|
+
alias_method :before_all, :global_setup
|
130
|
+
alias_method :after_all, :global_setup
|
131
|
+
|
132
|
+
alias_method :it, :test
|
133
|
+
alias_method :should, :test
|
134
|
+
alias_method :scenario, :test
|
135
|
+
end
|
136
|
+
|
137
|
+
# Initialize a new instance of a single test. This test can be run in
|
138
|
+
# isolation by calling TestCase#run.
|
139
|
+
def initialize(name, &block)
|
140
|
+
@test = block
|
141
|
+
@name = name
|
142
|
+
end
|
143
|
+
|
144
|
+
# Run a test in isolation. Any +setup+ and +teardown+ blocks defined for
|
145
|
+
# this test case will be run as expected.
|
146
|
+
#
|
147
|
+
# You need to provide a Runner instance to handle errors/pending tests/etc.
|
148
|
+
#
|
149
|
+
# If the test's block is nil, then the test will be marked as pending and
|
150
|
+
# nothing will be run.
|
151
|
+
def run(runner)
|
152
|
+
@runner = runner
|
153
|
+
|
154
|
+
runner.report(self) do
|
155
|
+
pending if test.nil?
|
156
|
+
|
157
|
+
setup
|
158
|
+
instance_eval(&test)
|
159
|
+
teardown
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Ensure a condition is met. This will raise AssertionFailed if the
|
164
|
+
# condition isn't met. You can override the default failure message
|
165
|
+
# by passing it as an argument.
|
166
|
+
def assert(condition, message="Expected condition to be satisfied")
|
167
|
+
@runner.assert(condition, message)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Provided for Test::Unit compatibility, this lets you include
|
171
|
+
# Test::Unit::Assertions and everything works seamlessly.
|
172
|
+
def assert_block(message="Expected condition to be satisified") #:nodoc:
|
173
|
+
assert(yield, message)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Make the test be ignored as pending. You can override the default message
|
177
|
+
# that will be sent to the report by passing it as an argument.
|
178
|
+
def pending(message="Not Yet Implemented")
|
179
|
+
raise Pending, message
|
180
|
+
end
|
181
|
+
|
182
|
+
# Name of the test
|
183
|
+
def name
|
184
|
+
@name
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def setup #:nodoc:
|
190
|
+
end
|
191
|
+
|
192
|
+
def teardown #:nodoc:
|
193
|
+
end
|
194
|
+
|
195
|
+
def test
|
196
|
+
@test
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.sanitize_description(description)
|
200
|
+
"Test#{description.gsub(/\W+/, ' ').strip.gsub(/(^| )(\w)/) { $2.upcase }}".to_sym
|
201
|
+
end
|
202
|
+
private_class_method :sanitize_description
|
203
|
+
|
204
|
+
def self.do_global_setup
|
205
|
+
end
|
206
|
+
private_class_method :do_global_setup
|
207
|
+
|
208
|
+
def self.do_global_teardown
|
209
|
+
end
|
210
|
+
private_class_method :do_global_teardown
|
211
|
+
|
212
|
+
def self.description #:nodoc:
|
213
|
+
parent = ancestors[1..-1].detect {|a| a < Protest::TestCase }
|
214
|
+
"#{parent.description rescue nil} #{@description}".strip
|
215
|
+
end
|
216
|
+
|
217
|
+
def self.inherited(child)
|
218
|
+
Protest.add_test_case(child)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Protest
|
2
|
+
# Encapsulates the relevant information about a test. Useful for certain
|
3
|
+
# reports.
|
4
|
+
class Test
|
5
|
+
# Instance of the test case that was run.
|
6
|
+
attr_reader :test
|
7
|
+
|
8
|
+
# Name of the test that passed. Useful for certain reports.
|
9
|
+
attr_reader :test_name
|
10
|
+
|
11
|
+
def initialize(test) #:nodoc:
|
12
|
+
@test = test
|
13
|
+
@test_name = test.name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Mixin for tests that had an error (this could be either a failed assertion,
|
18
|
+
# unrescued exceptions, or just a pending tests.)
|
19
|
+
module TestWithErrors
|
20
|
+
# The triggered exception (AssertionFailed, Pending, or any
|
21
|
+
# subclass of Exception in the case of an ErroredTest.)
|
22
|
+
attr_reader :error
|
23
|
+
|
24
|
+
# Message with which it failed the assertion.
|
25
|
+
def error_message
|
26
|
+
error.message
|
27
|
+
end
|
28
|
+
|
29
|
+
# Line of the file where the assertion failed.
|
30
|
+
def line
|
31
|
+
backtrace.first.split(":")[1]
|
32
|
+
end
|
33
|
+
|
34
|
+
# File where the assertion failed.
|
35
|
+
def file
|
36
|
+
backtrace.first.split(":")[0]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Filtered backtrace of the assertion. See Protest::Utils::BacktraceFilter
|
40
|
+
# for details on the filtering.
|
41
|
+
def backtrace
|
42
|
+
@backtrace ||= Utils::BacktraceFilter.filter(raw_backtrace)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Raw backtrace, as provided by the error.
|
46
|
+
def raw_backtrace
|
47
|
+
error.backtrace
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Encapsulate the relevant information for a test that passed.
|
52
|
+
class PassedTest < Test
|
53
|
+
end
|
54
|
+
|
55
|
+
# Encapsulates the relevant information for a test which failed an
|
56
|
+
# assertion.
|
57
|
+
class FailedTest < Test
|
58
|
+
include TestWithErrors
|
59
|
+
|
60
|
+
def initialize(test, error) #:nodoc:
|
61
|
+
super(test)
|
62
|
+
@error = error
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Encapsulates the relevant information for a test which raised an
|
67
|
+
# unrescued exception.
|
68
|
+
class ErroredTest < Test
|
69
|
+
include TestWithErrors
|
70
|
+
|
71
|
+
def initialize(test, error) #:nodoc:
|
72
|
+
super(test)
|
73
|
+
@error = error
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Encapsulates the relevant information for a test that the user marked as
|
78
|
+
# pending.
|
79
|
+
class PendingTest < Test
|
80
|
+
include TestWithErrors
|
81
|
+
|
82
|
+
# Message passed to TestCase#pending, if any.
|
83
|
+
alias_method :pending_message, :error_message
|
84
|
+
|
85
|
+
def initialize(test, error) #:nodoc:
|
86
|
+
super(test)
|
87
|
+
@error = error
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Protest
|
2
|
+
module Utils
|
3
|
+
# Small utility object to filter an error's backtrace and remove any mention
|
4
|
+
# of Testicle's own files.
|
5
|
+
module BacktraceFilter
|
6
|
+
# Path to the library's 'lib' dir.
|
7
|
+
BASE_PATH = /^#{Regexp.escape(File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__)))))}/
|
8
|
+
|
9
|
+
# Filter the backtrace, removing any reference to files located in
|
10
|
+
# BASE_PATH.
|
11
|
+
def self.filter(backtrace)
|
12
|
+
backtrace.reject do |line|
|
13
|
+
file = line.split(":").first
|
14
|
+
File.expand_path(file) =~ BASE_PATH
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Protest
|
2
|
+
module Utils
|
3
|
+
# Mixin that provides colorful output to your console based reports. This uses
|
4
|
+
# bash's escape sequences, so it won't work on windows.
|
5
|
+
#
|
6
|
+
# TODO: Make this work on windows with ansicolor or whatever the gem is named
|
7
|
+
module ColorfulOutput
|
8
|
+
# Returns a hash with the color values for different states. Override this
|
9
|
+
# method safely to change the output colors. The defaults are:
|
10
|
+
#
|
11
|
+
# :passed:: Light green
|
12
|
+
# :pending:: Light yellow
|
13
|
+
# :errored:: Light purple
|
14
|
+
# :failed:: Light red
|
15
|
+
#
|
16
|
+
# See http://www.hypexr.org/bash_tutorial.php#colors for a description of
|
17
|
+
# Bash color codes.
|
18
|
+
def self.colors
|
19
|
+
{ :passed => "1;32",
|
20
|
+
:pending => "1;33",
|
21
|
+
:errored => "1;35",
|
22
|
+
:failed => "1;31" }
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
# Whether to use colors in the output or not. The default is +true+.
|
27
|
+
attr_accessor :colorize
|
28
|
+
end
|
29
|
+
|
30
|
+
self.colorize = true
|
31
|
+
|
32
|
+
# Print the string followed by a newline to whatever IO stream is defined in
|
33
|
+
# the method #stream using the correct color depending on the state passed.
|
34
|
+
def puts(string=nil, state=:normal)
|
35
|
+
if string.nil? # calling IO#puts with nil is not the same as with no args
|
36
|
+
stream.puts
|
37
|
+
else
|
38
|
+
stream.puts colorize(string, state)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Print the string to whatever IO stream is defined in the method #stream
|
43
|
+
# using the correct color depending on the state passed.
|
44
|
+
def print(string=nil, state=:normal)
|
45
|
+
if string.nil? # calling IO#puts with nil is not the same as with no args
|
46
|
+
stream.print
|
47
|
+
else
|
48
|
+
stream.print colorize(string, state)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def colorize(string, state)
|
55
|
+
if state == :normal || !ColorfulOutput.colorize
|
56
|
+
string
|
57
|
+
else
|
58
|
+
"\033[#{color_for_state(state)}m#{string}\033[0m"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def color_for_state(state)
|
63
|
+
ColorfulOutput.colors.fetch(state)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Protest
|
2
|
+
module Utils
|
3
|
+
# Mixin that provides summaries for your text based test runs.
|
4
|
+
module Summaries
|
5
|
+
# Call on +:end+ to output the amount of tests (passed, pending, failed
|
6
|
+
# and errored), the amount of assertions, and the time elapsed.
|
7
|
+
#
|
8
|
+
# For example:
|
9
|
+
#
|
10
|
+
# on :end do |report|
|
11
|
+
# report.puts
|
12
|
+
# report.summarize_test_totals
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# This relies on the public Report API, and on the presence of a #puts
|
16
|
+
# method to write to whatever source you are writing your report.
|
17
|
+
def summarize_test_totals
|
18
|
+
puts test_totals
|
19
|
+
puts running_time
|
20
|
+
end
|
21
|
+
|
22
|
+
# Call on +:end+ to print a list of pending tests, including file and line
|
23
|
+
# number where the call to TestCase#pending+ was made.
|
24
|
+
#
|
25
|
+
# It will not output anything if there weren't any pending tests.
|
26
|
+
#
|
27
|
+
# For example:
|
28
|
+
#
|
29
|
+
# on :end do |report|
|
30
|
+
# report.puts
|
31
|
+
# report.summarize_pending_tests
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# This relies on the public Report API, and on the presence of a #puts
|
35
|
+
# method to write to whatever source you are writing your report.
|
36
|
+
def summarize_pending_tests
|
37
|
+
return if pendings.empty?
|
38
|
+
|
39
|
+
puts "Pending tests:"
|
40
|
+
puts
|
41
|
+
|
42
|
+
pad_indexes = pendings.size.to_s.size
|
43
|
+
pendings.each_with_index do |pending, index|
|
44
|
+
puts " #{pad(index+1, pad_indexes)}) #{pending.test_name} (#{pending.pending_message})", :pending
|
45
|
+
puts indent("On line #{pending.line} of `#{pending.file}'", 6 + pad_indexes), :pending
|
46
|
+
puts
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Call on +:end+ to print a list of failures (failed assertions) and errors
|
51
|
+
# (unrescued exceptions), including file and line number where the test
|
52
|
+
# failed, and a short backtrace.
|
53
|
+
#
|
54
|
+
# It will not output anything if there weren't any pending tests.
|
55
|
+
#
|
56
|
+
# For example:
|
57
|
+
#
|
58
|
+
# on :end do |report|
|
59
|
+
# report.puts
|
60
|
+
# report.summarize_pending_tests
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# This relies on the public Report API, and on the presence of a #puts
|
64
|
+
# method to write to whatever source you are writing your report.
|
65
|
+
def summarize_errors
|
66
|
+
return if failures_and_errors.empty?
|
67
|
+
|
68
|
+
puts "Failures:"
|
69
|
+
puts
|
70
|
+
|
71
|
+
pad_indexes = failures_and_errors.size.to_s.size
|
72
|
+
failures_and_errors.each_with_index do |error, index|
|
73
|
+
colorize_as = ErroredTest === error ? :errored : :failed
|
74
|
+
puts " #{pad(index+1, pad_indexes)}) #{test_type(error)}: `#{error.test_name}' (on line #{error.line} of `#{error.file}')", colorize_as
|
75
|
+
puts indent("With `#{error.error_message}'", 6 + pad_indexes), colorize_as
|
76
|
+
indent(error.backtrace[0..2], 6 + pad_indexes).each {|backtrace| puts backtrace, colorize_as }
|
77
|
+
puts
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def running_time
|
84
|
+
"Ran in #{time_elapsed} seconds"
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_totals
|
88
|
+
"%d test%s, %d assertion%s (%d passed, %d pending, %d failed, %d errored)" % [total_tests,
|
89
|
+
total_tests == 1 ? "" : "s",
|
90
|
+
assertions,
|
91
|
+
assertions == 1 ? "" : "s",
|
92
|
+
passes.size,
|
93
|
+
pendings.size,
|
94
|
+
failures.size,
|
95
|
+
errors.size]
|
96
|
+
end
|
97
|
+
|
98
|
+
def indent(strings, size=2, indent_with=" ")
|
99
|
+
Array(strings).map do |str|
|
100
|
+
str.to_s.split("\n").map {|s| indent_with * size + s }.join("\n")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def pad(str, amount)
|
105
|
+
" " * (amount - str.to_s.size) + str.to_s
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_type(test)
|
109
|
+
case test # order is important since ErroredTest < FailedTest
|
110
|
+
when ErroredTest; "Error"
|
111
|
+
when FailedTest; "Failure"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/protest.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "protest"
|
3
|
+
s.version = "0.2"
|
4
|
+
s.date = "2009-09-11"
|
5
|
+
|
6
|
+
s.description = "Protest is a tiny, simple, and easy-to-extend test framework"
|
7
|
+
s.summary = s.description
|
8
|
+
s.homepage = "http://github.com/foca/protest"
|
9
|
+
|
10
|
+
s.authors = ["Nicolás Sanguinetti"]
|
11
|
+
s.email = "contacto@nicolassanguinetti.info"
|
12
|
+
|
13
|
+
s.require_paths = ["lib"]
|
14
|
+
s.rubyforge_project = "protest"
|
15
|
+
s.has_rdoc = true
|
16
|
+
s.rubygems_version = "1.3.1"
|
17
|
+
|
18
|
+
s.files = %w[
|
19
|
+
.gitignore
|
20
|
+
LICENSE
|
21
|
+
README.rdoc
|
22
|
+
Rakefile
|
23
|
+
protest.gemspec
|
24
|
+
lib/protest.rb
|
25
|
+
lib/protest/utils.rb
|
26
|
+
lib/protest/utils/backtrace_filter.rb
|
27
|
+
lib/protest/utils/summaries.rb
|
28
|
+
lib/protest/utils/colorful_output.rb
|
29
|
+
lib/protest/test_case.rb
|
30
|
+
lib/protest/tests.rb
|
31
|
+
lib/protest/runner.rb
|
32
|
+
lib/protest/report.rb
|
33
|
+
lib/protest/reports.rb
|
34
|
+
lib/protest/reports/progress.rb
|
35
|
+
lib/protest/reports/documentation.rb
|
36
|
+
lib/protest/rails.rb
|
37
|
+
]
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: protest
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.2"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Nicol\xC3\xA1s Sanguinetti"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-09-11 00:00:00 -03:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Protest is a tiny, simple, and easy-to-extend test framework
|
17
|
+
email: contacto@nicolassanguinetti.info
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- LICENSE
|
27
|
+
- README.rdoc
|
28
|
+
- Rakefile
|
29
|
+
- protest.gemspec
|
30
|
+
- lib/protest.rb
|
31
|
+
- lib/protest/utils.rb
|
32
|
+
- lib/protest/utils/backtrace_filter.rb
|
33
|
+
- lib/protest/utils/summaries.rb
|
34
|
+
- lib/protest/utils/colorful_output.rb
|
35
|
+
- lib/protest/test_case.rb
|
36
|
+
- lib/protest/tests.rb
|
37
|
+
- lib/protest/runner.rb
|
38
|
+
- lib/protest/report.rb
|
39
|
+
- lib/protest/reports.rb
|
40
|
+
- lib/protest/reports/progress.rb
|
41
|
+
- lib/protest/reports/documentation.rb
|
42
|
+
- lib/protest/rails.rb
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://github.com/foca/protest
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project: protest
|
67
|
+
rubygems_version: 1.3.2
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Protest is a tiny, simple, and easy-to-extend test framework
|
71
|
+
test_files: []
|
72
|
+
|