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