testify 0.0.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/Gemfile +11 -0
- data/LICENSE +1 -1
- data/README.rdoc +100 -1
- data/Rakefile +20 -19
- data/VERSION +1 -0
- data/lib/framework.rb +115 -0
- data/lib/middleware.rb +20 -0
- data/lib/runner.rb +138 -0
- data/lib/test_result.rb +18 -0
- data/lib/testify.rb +41 -0
- data/samples/README +9 -0
- data/samples/awesome_runner.rb +28 -0
- data/samples/awesome_test_framework.rb +51 -0
- data/samples/awesome_tests/asplode.aws +3 -0
- data/samples/awesome_tests/failing.aws +3 -0
- data/samples/awesome_tests/passing.aws +3 -0
- data/samples/dots.rb +31 -0
- data/samples/summary.rb +19 -0
- data/spec/framework_spec.rb +81 -0
- data/spec/middleware_spec.rb +5 -0
- data/spec/runner_spec.rb +101 -0
- data/spec/sample_framework.rb +33 -0
- data/spec/sample_tests/failing_tests +7 -0
- data/spec/sample_tests/mixed_tests +11 -0
- data/spec/sample_tests/passing_tests +11 -0
- data/spec/sample_tests/test_helper +5 -0
- data/spec/spec_helper.rb +12 -7
- data/spec/test_result_spec.rb +11 -0
- data/spec/testify_spec.rb +19 -3
- metadata +72 -19
- data/.gitignore +0 -21
- data/spec/spec.opts +0 -1
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-c
|
data/Gemfile
ADDED
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -2,6 +2,105 @@
|
|
2
2
|
|
3
3
|
Testify is a test framework framework. Think "Rack for testing."
|
4
4
|
|
5
|
+
== Overview
|
6
|
+
|
7
|
+
Like Rack, Testify is typically going to be run as a stack of applications that
|
8
|
+
implement a +call+ method. In typical usage, the stack will be assembled by a
|
9
|
+
Runner and consist of a Framework and optionally some middleware, but this
|
10
|
+
isn't a requirement.
|
11
|
+
|
12
|
+
The source and detailed documentation can be found at
|
13
|
+
http://github.com/djspinmonkey/testify and
|
14
|
+
http://rdoc.info/projects/djspinmonkey/testify, respectively.
|
15
|
+
|
16
|
+
== Testify Applications
|
17
|
+
|
18
|
+
A Testify Application is any object that implements a +call+ method accepting a
|
19
|
+
single argument. The one argument will be a hash (canonically called env), and
|
20
|
+
the return value should be an array of TestResult objects.
|
21
|
+
|
22
|
+
=== The +env+ Hash
|
23
|
+
|
24
|
+
The +env+ hash should be an instance of Hash with one of the following keys
|
25
|
+
defined:
|
26
|
+
|
27
|
+
[:files] an array of filenames containing the tests
|
28
|
+
[:path] a path containing the files containing the tests
|
29
|
+
|
30
|
+
In addition, all of the following keys should be defined:
|
31
|
+
|
32
|
+
[:testify_version] An array of integers representing this version of Testify
|
33
|
+
[:hooks] A hash of callbacks - see below.
|
34
|
+
|
35
|
+
Given that Testify is still at a fairly early stage of development, it is quite
|
36
|
+
possible that more required keys will be added later.
|
37
|
+
|
38
|
+
=== Middleware
|
39
|
+
|
40
|
+
Middleware will generally either modify the env hash before the framework gets
|
41
|
+
it (eg, to execute only a subset of tests), or modify the TestResult objects on
|
42
|
+
the way back out (eg, to colorize the result text). If you want to respond to
|
43
|
+
a particular event as it happens (eg, send a Growl notification when a test
|
44
|
+
fails), it is probably best to do this by adding a hook rather than looking at
|
45
|
+
the returned TestResult objects, since call() will not return until all the
|
46
|
+
tests have finished running.
|
47
|
+
|
48
|
+
=== Frameworks
|
49
|
+
|
50
|
+
A Framework object is responsible for actually running the tests and generating
|
51
|
+
TestResult objects. Frameworks are required to respect all of the hooks
|
52
|
+
described below. Generally, Frameworks should inherit from
|
53
|
+
Testify::Framework::Base and should have at least one alias in order to benefit
|
54
|
+
from all of Testify's built-in functionality, but neither of these is a
|
55
|
+
requirement. (See the Aliasable module in the Classy gem for a more thorough
|
56
|
+
description of aliases. http://github.com/djspinmonkey/classy)
|
57
|
+
|
58
|
+
At least initially, most Framework classes will likely be adaptors for existing
|
59
|
+
test frameworks (RSpec, Test::Unit, etc).
|
60
|
+
|
61
|
+
=== Hooks
|
62
|
+
|
63
|
+
env[:hooks] is a hash containing arrays of callable objects (usually Procs,
|
64
|
+
but anything responding to +call+ will work) which will be called in specific
|
65
|
+
circumstances. The following keys should supported:
|
66
|
+
|
67
|
+
[:before_all] Will be called before running any tests. An array of Test
|
68
|
+
objects will be passed in. A middleware app might use this hook
|
69
|
+
instead of simply running immediately if it needed access to
|
70
|
+
individual tests instead of just files, or if it needed to run
|
71
|
+
after all other middleware. For example, if some other
|
72
|
+
middleware later in the stack were excluding some subset of tests
|
73
|
+
from running, you might need to be sure you were operating only
|
74
|
+
on the tests that would actually be run.
|
75
|
+
[:after_all] Will be called after running all tests. An array of TestResult
|
76
|
+
objects will be passed in. Middleware setting this hook should
|
77
|
+
probably just work with the array of TestResults returned from
|
78
|
+
calling the next Testify app on the stack instead, unless it
|
79
|
+
needs to run before the results pass through any other
|
80
|
+
middleware. Note that there is no guarantee another hook may not
|
81
|
+
be added prior to yours, however.
|
82
|
+
[:before_each] Will be called before running each individual test. A single
|
83
|
+
Test object will be passed in.
|
84
|
+
[:after_each] Will be called after running each individual test. A single
|
85
|
+
TestResult object will be passed in.
|
86
|
+
[:after_status] Points to a hash containing symbols as the keys, and arrays of
|
87
|
+
callable objects to be called whenever a TestResult is generated
|
88
|
+
with the status corresponding to that symbol. The TestResult
|
89
|
+
will be passed in.
|
90
|
+
|
91
|
+
If using the values provided by +Testify.env_defaults+, values for the first
|
92
|
+
four keys will be initialized to empty arrays, and +:after_status+ will be
|
93
|
+
initialized to an empty hash.
|
94
|
+
|
95
|
+
Like the +env+ hash, this is highly subject to change at this point.
|
96
|
+
|
97
|
+
== TODO
|
98
|
+
|
99
|
+
* Write adaptors for common test frameworks (rspec and minitest first, probably).
|
100
|
+
* Write an autotest-like Runner
|
101
|
+
* Write some useful middleware (growl notifications, colorized output, etc)
|
102
|
+
* World domination!
|
103
|
+
|
5
104
|
== Note on Patches/Pull Requests
|
6
105
|
|
7
106
|
* Fork the project.
|
@@ -15,4 +114,4 @@ Testify is a test framework framework. Think "Rack for testing."
|
|
15
114
|
|
16
115
|
== Copyright
|
17
116
|
|
18
|
-
Copyright (c)
|
117
|
+
Copyright (c) 2010 John Hyland. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
|
+
require 'rspec/core/rake_task'
|
3
4
|
|
4
5
|
begin
|
5
6
|
require 'jeweler'
|
@@ -9,7 +10,8 @@ begin
|
|
9
10
|
#gem.description = %Q{TODO: longer description of your gem}
|
10
11
|
gem.email = "github@djspinmonkey.com"
|
11
12
|
gem.homepage = "http://github.com/djspinmonkey/testify"
|
12
|
-
gem.authors = ["John Hyland"
|
13
|
+
gem.authors = ["John Hyland"]
|
14
|
+
gem.add_dependency "classy", ">= 1.0.0"
|
13
15
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
16
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
17
|
end
|
@@ -18,28 +20,27 @@ rescue LoadError
|
|
18
20
|
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
21
|
end
|
20
22
|
|
21
|
-
|
22
|
-
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
-
spec.libs << 'lib' << 'spec'
|
24
|
-
spec.spec_files = FileList['spec/**/*_spec.rb']
|
23
|
+
RSpec::Core::RakeTask.new do |t|
|
25
24
|
end
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
spec.rcov = true
|
26
|
+
RSpec::Core::RakeTask.new(:coverage) do |t|
|
27
|
+
t.rcov = true
|
28
|
+
t.rcov_opts = ['--exclude', 'spec']
|
31
29
|
end
|
32
30
|
|
33
|
-
task :spec
|
31
|
+
task :spec
|
34
32
|
|
35
33
|
task :default => :spec
|
36
34
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
rdoc.
|
45
|
-
|
35
|
+
# This is generating some annoying warnings - commented out for now until I
|
36
|
+
# figure out how to quiet it down.
|
37
|
+
#
|
38
|
+
#require 'rdoc/task'
|
39
|
+
#RDoc::Task.new do |rdoc|
|
40
|
+
# version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
41
|
+
#
|
42
|
+
# rdoc.rdoc_dir = 'rdoc'
|
43
|
+
# rdoc.title = "testify #{version}"
|
44
|
+
# rdoc.rdoc_files.include('README*')
|
45
|
+
# rdoc.rdoc_files.include('lib/**/*.rb')
|
46
|
+
#end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/lib/framework.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
module Testify
|
2
|
+
# The module which all Framework classes are recommended to inherit.
|
3
|
+
# Framework provides a few utility methods and a means to keep track of all
|
4
|
+
# Framework classes (via Aliasable). Framework objects are Testify apps, so
|
5
|
+
# subclasses must implement a +call+ method as described in the README.rdoc
|
6
|
+
# file. It is also good practice to declare at least one alias (see the
|
7
|
+
# Aliasable module in the Classy gem for more details).
|
8
|
+
#
|
9
|
+
module Framework
|
10
|
+
include Aliasable
|
11
|
+
|
12
|
+
# The default set of statuses
|
13
|
+
#
|
14
|
+
DEFAULT_STATUSES = [ :passed, :pending, :failed, :error ]
|
15
|
+
|
16
|
+
# Extend the ClassMethods module and set the default statuses.
|
17
|
+
#
|
18
|
+
# :nodoc:
|
19
|
+
#
|
20
|
+
def self.included( klass )
|
21
|
+
klass.extend Testify::Framework::ClassMethods
|
22
|
+
klass.statuses *Testify::Framework::DEFAULT_STATUSES.dup
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns an array of absolute paths to each file defined by an +env+
|
27
|
+
# hash. The default implementation either returns the array of files, if
|
28
|
+
# :files is defined in the hash, or returns every file found in a
|
29
|
+
# traversal of the path in the :path key. Raises an exception if neither
|
30
|
+
# is defined, since that is not a valid +env+ hash.
|
31
|
+
#
|
32
|
+
# If a particular framework should only process files with names matching
|
33
|
+
# a particular glob pattern (eg, RSpec only wants files that match
|
34
|
+
# `*_spec.rb`), it can specify this with +file_pattern+.
|
35
|
+
#
|
36
|
+
def files( env )
|
37
|
+
return env[:files] if env.include? :files
|
38
|
+
raise(ArgumentError, "env hash must include either :files or :path") unless env.include? :path
|
39
|
+
|
40
|
+
file_glob = self.class.class_eval { @file_pattern } || '*'
|
41
|
+
Dir.glob(File.join(env[:path], '**', file_glob))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Run all the appropriate hooks before running any tests at all.
|
45
|
+
#
|
46
|
+
def run_before_all_hooks( env )
|
47
|
+
env[:hooks][:before_all].each { |hook| hook.call }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Run all the appropriate hooks before running each test.
|
51
|
+
#
|
52
|
+
def run_before_each_hooks( env )
|
53
|
+
env[:hooks][:before_each].each { |hook| hook.call }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Run all the appropriate hooks after running all the tests
|
57
|
+
#
|
58
|
+
def run_after_all_hooks( env, results )
|
59
|
+
env[:hooks][:after_all].each { |hook| hook.call(results) }
|
60
|
+
end
|
61
|
+
|
62
|
+
# Run all the appropriate hooks after running each test individually
|
63
|
+
# (including the hooks for a particular status).
|
64
|
+
#
|
65
|
+
def run_after_each_hooks( env, result )
|
66
|
+
hooks = env[:hooks][:after_each]
|
67
|
+
hooks += env[:hooks][:after_status][result.status].to_a # .to_a in case it's nil
|
68
|
+
hooks.each { |hook| hook.call(result) }
|
69
|
+
end
|
70
|
+
|
71
|
+
module ClassMethods
|
72
|
+
# Gets and sets an array of symbols corresponding to the possible
|
73
|
+
# TestResult statuses that might be set by this Framework.
|
74
|
+
#
|
75
|
+
# For example, a framework with tests that can only pass or fail might
|
76
|
+
# like like this:
|
77
|
+
#
|
78
|
+
# class SomeFramework
|
79
|
+
# include Testify::Framework
|
80
|
+
#
|
81
|
+
# statuses :passed, :failed
|
82
|
+
#
|
83
|
+
# ...
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# SomeFramework.statuses # => [ :passed, :failed ]
|
87
|
+
#
|
88
|
+
def statuses( *new_statuses )
|
89
|
+
if new_statuses.any?
|
90
|
+
@statuses = new_statuses
|
91
|
+
end
|
92
|
+
@statuses
|
93
|
+
end
|
94
|
+
|
95
|
+
# Accepts a glob pattern that limits the paths returned by `#files`.
|
96
|
+
# Only paths with filenames that match this pattern will be returned by
|
97
|
+
# +.files+.
|
98
|
+
#
|
99
|
+
# For example, if all your framework's test files should end in
|
100
|
+
# _mytests.rb, you might do this:
|
101
|
+
#
|
102
|
+
# class MyTestFramework
|
103
|
+
# include Testify::Framework
|
104
|
+
#
|
105
|
+
# file_pattern '*_mytests.rb'
|
106
|
+
#
|
107
|
+
# ...
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
def file_pattern( pattern )
|
111
|
+
self.class_eval { @file_pattern = pattern }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/middleware.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Testify
|
2
|
+
module Middleware
|
3
|
+
include Aliasable
|
4
|
+
|
5
|
+
# By default, Testify middleware is initialized with only the next app on
|
6
|
+
# the stack. If you override this, you may also need to override
|
7
|
+
# Runner.construct_app_stack.
|
8
|
+
#
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
# By default, the middleware base class just calls the next app on the
|
14
|
+
# stack - you will almost certainly want to override this method.
|
15
|
+
#
|
16
|
+
def call(env)
|
17
|
+
@app.call(env)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/runner.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
module Testify
|
2
|
+
|
3
|
+
# Runner is a Testify app intended to sit at the top of a Testify stack and
|
4
|
+
# provide a simple mechanism for running a set of tests. If you're writing
|
5
|
+
# an autotest-like application, Runner is what you would use. Runner uses
|
6
|
+
# Templatable from the Classy gem, so you can either subclass Runner and use
|
7
|
+
# DSL-like class methods to define its behavior, or instantiate it directly
|
8
|
+
# and configure the instance.
|
9
|
+
#
|
10
|
+
# For example,
|
11
|
+
#
|
12
|
+
# class SampleRunner
|
13
|
+
# include Testify::Runner
|
14
|
+
#
|
15
|
+
# framework :rspec
|
16
|
+
# middleware :growl, :red_green
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# @runner = SampleRunner.new
|
20
|
+
#
|
21
|
+
# would be equivalent to
|
22
|
+
#
|
23
|
+
# @runner = Testify::Runner::Base.new
|
24
|
+
# @runner.framework = Testify::Framework::RspecAdaptor
|
25
|
+
# @runner.middleware = [Testify::Middleware::Growl, Testify::Middleware::RedGreen]
|
26
|
+
#
|
27
|
+
# Which approach is best depends on your needs, or you can use a mixture of
|
28
|
+
# the two.
|
29
|
+
#
|
30
|
+
module Runner
|
31
|
+
def self.included( klass )
|
32
|
+
klass.class_eval do
|
33
|
+
extend Templatable
|
34
|
+
extend Testify::Runner::ClassMethods
|
35
|
+
|
36
|
+
attr_accessor :status, :framework_instance, :test_results
|
37
|
+
templatable_attr :framework, :middleware
|
38
|
+
|
39
|
+
@@middleware = []
|
40
|
+
end
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
# Allows the framework to be specified on an instance of Runner. See +.framework+.
|
45
|
+
#
|
46
|
+
def framework= (fw)
|
47
|
+
@framework = Testify::Framework::Base.find(fw)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Allows the middleware array to be specified for an instance of Runner. See +.middleware+.
|
51
|
+
#
|
52
|
+
# Note: The class method accepts a list, but the instance method must be passed an actual array.
|
53
|
+
#
|
54
|
+
# Example:
|
55
|
+
#
|
56
|
+
# runner = YourRunner.new
|
57
|
+
# runner.middleware = [:dots, :colorize]
|
58
|
+
#
|
59
|
+
def middleware=( middleware )
|
60
|
+
@middleware = middleware.map { |mw| Testify::Middleware.find(mw) }
|
61
|
+
end
|
62
|
+
|
63
|
+
# Constructs the stack of middleware and framework. If you are doing
|
64
|
+
# something unconventional (eg, creating middleware that requires
|
65
|
+
# additional initialization parameters), you may need to override this
|
66
|
+
# method.
|
67
|
+
#
|
68
|
+
def construct_app_stack
|
69
|
+
@framework_instance ||= framework.new
|
70
|
+
top_app = @framework_instance
|
71
|
+
|
72
|
+
middleware.each do |mw_class|
|
73
|
+
top_app = mw_class.new(top_app)
|
74
|
+
end
|
75
|
+
|
76
|
+
top_app
|
77
|
+
end
|
78
|
+
|
79
|
+
# Run the tests. Accepts a hash that will be merged in to the default env
|
80
|
+
# and passed to the Testify app stack.
|
81
|
+
#
|
82
|
+
def run( options = {} )
|
83
|
+
top_app = construct_app_stack
|
84
|
+
|
85
|
+
env = Testify.env_defaults.merge options
|
86
|
+
@test_results = top_app.call(env)
|
87
|
+
|
88
|
+
@status = @test_results.collect(&:status).max # XXX: Optimize?
|
89
|
+
|
90
|
+
@test_results
|
91
|
+
end
|
92
|
+
|
93
|
+
module ClassMethods
|
94
|
+
# Defines and/or returns the framework to use. This can be any class whose
|
95
|
+
# instances are Testify apps, and an instance of this class will be placed
|
96
|
+
# at the bottom of the Testify stack used by Runner#run. Typically, this
|
97
|
+
# will probably be a test framework built on Testify or (more likely, at
|
98
|
+
# least for now) a Testify adaptor for some existing framework. If the
|
99
|
+
# class descends from Testify::Framework::Base and defines an alias (eg,
|
100
|
+
# aka :rspec), you may use that alias instead of passing in the actual
|
101
|
+
# class.
|
102
|
+
#
|
103
|
+
# Example:
|
104
|
+
#
|
105
|
+
# class YourRunner < Testify::Runner::Base
|
106
|
+
# framework :rspec
|
107
|
+
# end
|
108
|
+
# @runner = YourRunner.new
|
109
|
+
# @runner.framework # <= Testify::Framework::RspecAdaptor
|
110
|
+
#
|
111
|
+
# Note that you can also specify the framework on an instance, using
|
112
|
+
# +#framework=+.
|
113
|
+
#
|
114
|
+
# @runner = Testify::Runner.new
|
115
|
+
# @runner.framework YourAwesomeTestifyFrameworkClass
|
116
|
+
#
|
117
|
+
def framework( fw = nil )
|
118
|
+
self.framework = Testify::Framework.find(fw) if fw
|
119
|
+
self.send(:class_variable_get, :@@framework)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Defines and/or returns the middleware to use, in the same manner as
|
123
|
+
# +.framework+. Note that this completely replaces any previous
|
124
|
+
# middleware specified for this class.
|
125
|
+
#
|
126
|
+
# Example:
|
127
|
+
#
|
128
|
+
# class YourRunner < Testify::Runner::Base
|
129
|
+
# middleware :dots, :colorize
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
def middleware( *middlewares )
|
133
|
+
self.middleware = middlewares.map { |mw| Testify::Middleware.find(mw) } unless middlewares.empty?
|
134
|
+
self.send(:class_variable_get, :@@middleware)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/test_result.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Testify
|
2
|
+
|
3
|
+
# A TestResult object represents the results of running a single test,
|
4
|
+
# whatever that means in the context of your framework.
|
5
|
+
#
|
6
|
+
class TestResult
|
7
|
+
attr_accessor :status, :message, :file, :line
|
8
|
+
|
9
|
+
def initialize (options = {})
|
10
|
+
@status = options[:status]
|
11
|
+
@message = options[:message]
|
12
|
+
@file = options[:file]
|
13
|
+
@line = options[:line]
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/lib/testify.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
# gems
|
2
|
+
require 'classy'
|
3
|
+
|
4
|
+
# Make sure we're grabbing the right version of everything.
|
5
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
6
|
+
|
7
|
+
require 'framework'
|
8
|
+
require 'runner'
|
9
|
+
require 'middleware'
|
10
|
+
require 'test_result'
|
11
|
+
|
12
|
+
# Put the LOAD_PATH back the way it was.
|
13
|
+
$LOAD_PATH.shift
|
14
|
+
|
15
|
+
# The Testify module provides some general-purpose functions that aren't
|
16
|
+
# specific to any single component.
|
17
|
+
module Testify
|
18
|
+
|
19
|
+
# Provides some reasonable defaults to create a new env hash. This method
|
20
|
+
# does not fill in :path or :files, so you'll need to explicitly specify one
|
21
|
+
# or the other.
|
22
|
+
#
|
23
|
+
def self.env_defaults
|
24
|
+
{ :testify_version => Testify.version,
|
25
|
+
:hooks => { :before_all => [],
|
26
|
+
:after_all => [],
|
27
|
+
:before_each => [],
|
28
|
+
:after_each => [],
|
29
|
+
:after_status => {},
|
30
|
+
}
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the current version as an array. (Eg, version 1.2.3 would be
|
35
|
+
# returned as [1, 2, 3].)
|
36
|
+
#
|
37
|
+
def self.version
|
38
|
+
File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).chomp.split('.').collect { |n| n.to_i }
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
data/samples/README
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
This is a sample Testify-based setup. The framework is called (imaginatively
|
2
|
+
enough) AwesomeTestFramework - this is the same level as RSpec or minitest.
|
3
|
+
The runner is called AwesomeRunner - think of the 'rspec' command or
|
4
|
+
'autotest'. And for flavor we've added some middleware to output valuable
|
5
|
+
information while the tests run and append some footer text at the end.
|
6
|
+
|
7
|
+
To see it in action, just run this:
|
8
|
+
|
9
|
+
./awesome_runner.rb awesome_tests
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../lib/testify'
|
2
|
+
require_relative 'awesome_test_framework'
|
3
|
+
require_relative 'dots'
|
4
|
+
require_relative 'summary'
|
5
|
+
|
6
|
+
class AwesomeRunner
|
7
|
+
include Testify::Runner
|
8
|
+
|
9
|
+
# Specify the framework we want to use. This alias is defined in
|
10
|
+
# AwesomeTestFramework.
|
11
|
+
#
|
12
|
+
framework :awesome
|
13
|
+
|
14
|
+
# Specify the middleware. Again, this is defined in RadMiddleware.
|
15
|
+
#
|
16
|
+
middleware :summary, :dots
|
17
|
+
end
|
18
|
+
|
19
|
+
# Instantiate the runner and run the tests.
|
20
|
+
#
|
21
|
+
tests_path = File.join(File.dirname(__FILE__), 'awesome_tests')
|
22
|
+
@runner = AwesomeRunner.new
|
23
|
+
results = @runner.run(:path => tests_path)
|
24
|
+
|
25
|
+
#puts "#{results.size} tests run"
|
26
|
+
#results.each do |res|
|
27
|
+
# puts "#{res.status} - #{res.message}"
|
28
|
+
#end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# A sample test framework built using Testify.
|
2
|
+
#
|
3
|
+
class AwesomeTestFramework
|
4
|
+
include Testify::Framework
|
5
|
+
|
6
|
+
# Specify an alias for this framework.
|
7
|
+
#
|
8
|
+
aka :awesome
|
9
|
+
|
10
|
+
# These are the statuses this framework might return, in increasing order of severity.
|
11
|
+
#
|
12
|
+
statuses :pass, :fail, :error
|
13
|
+
|
14
|
+
# This is what a test file for this framework looks like.
|
15
|
+
#
|
16
|
+
file_pattern '*.aws'
|
17
|
+
|
18
|
+
# This is where you do your thing. In a real test framework, you'd probably
|
19
|
+
# be testing assertions and setting up contexts and generally making the
|
20
|
+
# magic happen here (or at least calling the libraries where the magic
|
21
|
+
# happens).
|
22
|
+
#
|
23
|
+
# In this example, we're just reading test results out of a file and calling
|
24
|
+
# the appropriate hooks.
|
25
|
+
#
|
26
|
+
def call( env )
|
27
|
+
results = []
|
28
|
+
|
29
|
+
run_before_all_hooks(env)
|
30
|
+
|
31
|
+
files(env).each do |file|
|
32
|
+
open file do |f|
|
33
|
+
f.lines.each_with_index do |line, line_number|
|
34
|
+
run_before_each_hooks(env)
|
35
|
+
|
36
|
+
message, status = * line.split(':')
|
37
|
+
status = status.strip.to_sym
|
38
|
+
result = Testify::TestResult.new( :file => file, :line => line_number, :message => message, :status => status )
|
39
|
+
results << result
|
40
|
+
|
41
|
+
run_after_each_hooks(env, result)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
run_after_all_hooks(env, results)
|
47
|
+
|
48
|
+
results
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
data/samples/dots.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Output a dot every time a test passes, an F when it fails, or a E on an
|
2
|
+
# error. On any other status, it prints a ?.
|
3
|
+
#
|
4
|
+
class Dots
|
5
|
+
include Testify::Middleware
|
6
|
+
|
7
|
+
aka :dots
|
8
|
+
|
9
|
+
def call( env )
|
10
|
+
# Output dots (et al) as each test runs.
|
11
|
+
#
|
12
|
+
env[:hooks][:after_each].push(lambda do |result|
|
13
|
+
case result.status
|
14
|
+
when :pass
|
15
|
+
print '.'
|
16
|
+
when :fail
|
17
|
+
print 'F'
|
18
|
+
when :error
|
19
|
+
print 'E'
|
20
|
+
else
|
21
|
+
print '?'
|
22
|
+
end
|
23
|
+
end)
|
24
|
+
|
25
|
+
# Output a couple newlines at the end after all the tests have run.
|
26
|
+
#
|
27
|
+
env[:hooks][:after_all].push(lambda { |ignored| puts; puts })
|
28
|
+
|
29
|
+
@app.call(env)
|
30
|
+
end
|
31
|
+
end
|
data/samples/summary.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Output a summary of the test results after they've all been run.
|
2
|
+
#
|
3
|
+
class Summary
|
4
|
+
include Testify::Middleware
|
5
|
+
|
6
|
+
aka :summary
|
7
|
+
|
8
|
+
def call( env )
|
9
|
+
results = @app.call(env)
|
10
|
+
|
11
|
+
summary = Hash.new(0)
|
12
|
+
results.each { |res| summary[res.status] += 1 }
|
13
|
+
|
14
|
+
puts "Ran #{results.size} tests in all"
|
15
|
+
summary.each { |status, count| puts "#{count} tests with status #{status}" }
|
16
|
+
|
17
|
+
results
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe "Testify::Framework" do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
Testify::Framework.forget_aliases
|
7
|
+
|
8
|
+
destroy_class :VanillaFramework
|
9
|
+
class VanillaFramework
|
10
|
+
include Testify::Framework
|
11
|
+
aka :vanilla
|
12
|
+
end
|
13
|
+
@framework = VanillaFramework.new
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should be able to specify known aliases" do
|
17
|
+
Testify::Framework.find(:vanilla).should equal VanillaFramework
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should raise an ArgumentError if a given alias is already taken" do
|
21
|
+
lambda {
|
22
|
+
class ThisIsTheRepeat
|
23
|
+
include Testify::Framework
|
24
|
+
aka :vanilla
|
25
|
+
end
|
26
|
+
}.should raise_error(ArgumentError)
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should be able to specify a status ranking without affecting other Frameworks" do
|
31
|
+
class ChangedFramework
|
32
|
+
include Testify::Framework
|
33
|
+
statuses :passed, :failed, :exploded
|
34
|
+
end
|
35
|
+
|
36
|
+
VanillaFramework.statuses.should eql Testify::Framework::DEFAULT_STATUSES
|
37
|
+
ChangedFramework.statuses.should eql [ :passed, :failed, :exploded ]
|
38
|
+
end
|
39
|
+
|
40
|
+
context '#files' do
|
41
|
+
before do
|
42
|
+
@test_path = File.join(File.dirname(__FILE__), "sample_tests")
|
43
|
+
all_files = ['failing_tests', 'mixed_tests', 'passing_tests', 'test_helper']
|
44
|
+
test_files = all_files - ['test_helper']
|
45
|
+
@all_paths = all_files.collect { |f| File.join(File.dirname(__FILE__), 'sample_tests', f) }
|
46
|
+
@test_paths = test_files.collect { |f| File.join(File.dirname(__FILE__), 'sample_tests', f) }
|
47
|
+
end
|
48
|
+
|
49
|
+
after do
|
50
|
+
VanillaFramework.file_pattern nil
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should find files specified by :path" do
|
54
|
+
env = { :path => @test_path }
|
55
|
+
@framework.files(env).sort.should eql @all_paths
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should find files specified by :files" do
|
59
|
+
env = { :files => @all_paths }
|
60
|
+
@framework.files(env).sort.should eql @all_paths
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should raise an ArgumentError if neither :files nor :path is defined" do
|
64
|
+
env = {}
|
65
|
+
lambda {
|
66
|
+
@framework.files(env)
|
67
|
+
}.should raise_exception ArgumentError
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should respect the .file_pattern setting" do
|
71
|
+
class VanillaFramework
|
72
|
+
file_pattern '*_tests'
|
73
|
+
end
|
74
|
+
|
75
|
+
env = { :path => @test_path }
|
76
|
+
@framework.files(env).sort.should eql @test_paths
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
data/spec/runner_spec.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe "Testify::Runner" do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
Testify::Middleware.forget_aliases
|
7
|
+
Testify::Framework.forget_aliases
|
8
|
+
|
9
|
+
destroy_class :SomeMiddleware
|
10
|
+
class SomeMiddleware
|
11
|
+
include Testify::Middleware
|
12
|
+
aka :some_middleware
|
13
|
+
end
|
14
|
+
|
15
|
+
destroy_class :SomeMoreMiddleware
|
16
|
+
class SomeMoreMiddleware
|
17
|
+
include Testify::Middleware
|
18
|
+
aka :more_middleware
|
19
|
+
end
|
20
|
+
|
21
|
+
destroy_class :SomeTestFramework
|
22
|
+
class SomeTestFramework
|
23
|
+
include Testify::Framework
|
24
|
+
|
25
|
+
aka :some_test_framework
|
26
|
+
attr_accessor :env
|
27
|
+
|
28
|
+
def call (env)
|
29
|
+
@env = env
|
30
|
+
[Testify::TestResult.new(:message => "Testing", :status => :pass)]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
destroy_class :TestRunner
|
35
|
+
class TestRunner
|
36
|
+
include Testify::Runner
|
37
|
+
end
|
38
|
+
@runner = TestRunner.new
|
39
|
+
|
40
|
+
@test_path = File.expand_path(File.join(File.dirname(__FILE__), 'sample_tests'))
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should be able to specify a test framework by alias" do
|
44
|
+
class TestRunner
|
45
|
+
framework :some_test_framework
|
46
|
+
end
|
47
|
+
|
48
|
+
TestRunner.framework.should eql SomeTestFramework
|
49
|
+
@runner.framework.should eql SomeTestFramework
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should be able to specify a test framework by class" do
|
53
|
+
class TestRunner
|
54
|
+
framework SomeTestFramework
|
55
|
+
end
|
56
|
+
|
57
|
+
TestRunner.framework.should eql SomeTestFramework
|
58
|
+
@runner.framework.should eql SomeTestFramework
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should be able to specify middleware by alias" do
|
62
|
+
class TestRunner
|
63
|
+
middleware :some_middleware, :more_middleware
|
64
|
+
end
|
65
|
+
|
66
|
+
TestRunner.middleware.should eql [SomeMiddleware, SomeMoreMiddleware]
|
67
|
+
@runner.middleware.should eql [SomeMiddleware, SomeMoreMiddleware]
|
68
|
+
end
|
69
|
+
|
70
|
+
context "#run" do
|
71
|
+
it "should accept options and put them in the env hash" do
|
72
|
+
framework = SomeTestFramework.new
|
73
|
+
@runner.framework_instance = framework
|
74
|
+
@runner.run :foo => 'bar'
|
75
|
+
framework.env[:foo].should == 'bar'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "just created" do
|
80
|
+
it "should have a nil status" do
|
81
|
+
@runner.status.should be_nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "with a test framework defined" do
|
86
|
+
before do
|
87
|
+
@runner.framework = SampleFramework
|
88
|
+
end
|
89
|
+
|
90
|
+
context "after running" do
|
91
|
+
before do
|
92
|
+
test_path = File.join(File.dirname(__FILE__), "sample_tests")
|
93
|
+
@runner.run :path => test_path
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should have a status" do
|
97
|
+
@runner.status.should_not be_nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# A sample Testify framework intended to be used while testing Testify. A
|
2
|
+
# "test" consists of a line in the file, which should contain the status
|
3
|
+
# and optionally a colon and a message.
|
4
|
+
#
|
5
|
+
# Example test file:
|
6
|
+
# fail:The thing is not in the place.
|
7
|
+
# success
|
8
|
+
# pending:Need to implement cat napkins.
|
9
|
+
# success
|
10
|
+
#
|
11
|
+
class SampleFramework
|
12
|
+
include Testify::Framework
|
13
|
+
|
14
|
+
aka :sample
|
15
|
+
|
16
|
+
# Run the tests.
|
17
|
+
#
|
18
|
+
def call (env)
|
19
|
+
results = []
|
20
|
+
|
21
|
+
files(env).each do |file|
|
22
|
+
line_number = 0
|
23
|
+
File.open(file).each_line do |line|
|
24
|
+
line_number += 1
|
25
|
+
(status, message) = line.split(':')
|
26
|
+
results.push Testify::TestResult.new(:status => status.to_sym, :message => message, :file => file, :line => line_number)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
results
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require '
|
5
|
-
require 'spec/autorun'
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'testify')
|
2
|
+
require_relative '../lib/testify'
|
3
|
+
require_relative 'sample_framework'
|
4
|
+
require 'rspec'
|
6
5
|
|
7
|
-
|
8
|
-
|
6
|
+
RSpec.configure do |config|
|
7
|
+
end
|
8
|
+
|
9
|
+
# Doesn't work for anything inside a module (eg, if you try to remove
|
10
|
+
# Testify::Framework::RSpec this will totally blow up).
|
11
|
+
def destroy_class ( klass )
|
12
|
+
klass = klass.name.to_s if klass.kind_of? Class
|
13
|
+
Object.class_exec { remove_const klass } if Object.const_defined? klass
|
9
14
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe "Testify::TestResult" do
|
4
|
+
it "should be instantializable with a hash of attributes" do
|
5
|
+
res = Testify::TestResult.new(:file => 'woo_tests.rb', :line => 42, :status => :error, :message => "Your test asploded.")
|
6
|
+
res.status.should == :error
|
7
|
+
res.message.should == "Your test asploded."
|
8
|
+
res.file.should == 'woo_tests.rb'
|
9
|
+
res.line.should == 42
|
10
|
+
end
|
11
|
+
end
|
data/spec/testify_spec.rb
CHANGED
@@ -1,7 +1,23 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__)
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
2
|
|
3
3
|
describe "Testify" do
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
context '.env_defaults' do
|
6
|
+
it "should provide an env hash with some default values" do
|
7
|
+
defaults = Testify.env_defaults
|
8
|
+
|
9
|
+
# Verify the values on these ones
|
10
|
+
defaults[:testify_version].should == Testify.version
|
11
|
+
defaults[:hooks].should == { :before_all => [], :after_all => [], :before_each => [], :after_each => [], :after_status => {} }
|
12
|
+
end
|
6
13
|
end
|
14
|
+
|
15
|
+
context '.version' do
|
16
|
+
it "should return an array of three integers" do
|
17
|
+
version = Testify.version
|
18
|
+
version.should have(3).parts
|
19
|
+
version.each { |n| n.should be_an Integer }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
7
23
|
end
|
metadata
CHANGED
@@ -1,28 +1,61 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: testify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease:
|
5
|
+
version: 1.0.0
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- John Hyland
|
8
|
-
- Emily Price
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date:
|
14
|
-
default_executable:
|
13
|
+
date: 2011-10-31 00:00:00 Z
|
15
14
|
dependencies:
|
16
15
|
- !ruby/object:Gem::Dependency
|
17
|
-
name:
|
16
|
+
name: classy
|
17
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: jeweler
|
28
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
18
34
|
type: :development
|
19
|
-
|
20
|
-
version_requirements:
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: classy
|
39
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 1.0.0
|
45
|
+
type: :runtime
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rspec
|
50
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
21
52
|
requirements:
|
22
53
|
- - ">="
|
23
54
|
- !ruby/object:Gem::Version
|
24
55
|
version: 1.2.9
|
25
|
-
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *id004
|
26
59
|
description:
|
27
60
|
email: github@djspinmonkey.com
|
28
61
|
executables: []
|
@@ -34,42 +67,62 @@ extra_rdoc_files:
|
|
34
67
|
- README.rdoc
|
35
68
|
files:
|
36
69
|
- .document
|
37
|
-
- .
|
70
|
+
- .rspec
|
71
|
+
- Gemfile
|
38
72
|
- LICENSE
|
39
73
|
- README.rdoc
|
40
74
|
- Rakefile
|
75
|
+
- VERSION
|
76
|
+
- lib/framework.rb
|
77
|
+
- lib/middleware.rb
|
78
|
+
- lib/runner.rb
|
79
|
+
- lib/test_result.rb
|
41
80
|
- lib/testify.rb
|
42
|
-
-
|
81
|
+
- samples/README
|
82
|
+
- samples/awesome_runner.rb
|
83
|
+
- samples/awesome_test_framework.rb
|
84
|
+
- samples/awesome_tests/asplode.aws
|
85
|
+
- samples/awesome_tests/failing.aws
|
86
|
+
- samples/awesome_tests/passing.aws
|
87
|
+
- samples/dots.rb
|
88
|
+
- samples/summary.rb
|
89
|
+
- spec/framework_spec.rb
|
90
|
+
- spec/middleware_spec.rb
|
91
|
+
- spec/runner_spec.rb
|
92
|
+
- spec/sample_framework.rb
|
93
|
+
- spec/sample_tests/failing_tests
|
94
|
+
- spec/sample_tests/mixed_tests
|
95
|
+
- spec/sample_tests/passing_tests
|
96
|
+
- spec/sample_tests/test_helper
|
43
97
|
- spec/spec_helper.rb
|
98
|
+
- spec/test_result_spec.rb
|
44
99
|
- spec/testify_spec.rb
|
45
|
-
has_rdoc: true
|
46
100
|
homepage: http://github.com/djspinmonkey/testify
|
47
101
|
licenses: []
|
48
102
|
|
49
103
|
post_install_message:
|
50
|
-
rdoc_options:
|
51
|
-
|
104
|
+
rdoc_options: []
|
105
|
+
|
52
106
|
require_paths:
|
53
107
|
- lib
|
54
108
|
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
55
110
|
requirements:
|
56
111
|
- - ">="
|
57
112
|
- !ruby/object:Gem::Version
|
58
113
|
version: "0"
|
59
|
-
version:
|
60
114
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
61
116
|
requirements:
|
62
117
|
- - ">="
|
63
118
|
- !ruby/object:Gem::Version
|
64
119
|
version: "0"
|
65
|
-
version:
|
66
120
|
requirements: []
|
67
121
|
|
68
122
|
rubyforge_project:
|
69
|
-
rubygems_version: 1.
|
123
|
+
rubygems_version: 1.8.6
|
70
124
|
signing_key:
|
71
125
|
specification_version: 3
|
72
126
|
summary: Testify is a test framework framework. Think "Rack for testing."
|
73
|
-
test_files:
|
74
|
-
|
75
|
-
- spec/testify_spec.rb
|
127
|
+
test_files: []
|
128
|
+
|
data/.gitignore
DELETED
data/spec/spec.opts
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
--color
|