scope 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/scope.rb +146 -0
- data/scope.gemspec +21 -0
- metadata +81 -0
data/lib/scope.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require "minitest/unit"
|
2
|
+
|
3
|
+
module Scope
|
4
|
+
# A test case class which provides nested contexts. Subclasses will have the "setup", "teardown", and
|
5
|
+
# "should" methods available as class methods.
|
6
|
+
class TestCase < MiniTest::Unit::TestCase
|
7
|
+
# A map of test name => Context.
|
8
|
+
def self.context_for_test() @context_for_test end
|
9
|
+
|
10
|
+
def self.inherited(subclass)
|
11
|
+
# Calling Unit::TestCase's inherited() method is important, as that's how it registers test suites.
|
12
|
+
super
|
13
|
+
|
14
|
+
subclass.instance_eval do
|
15
|
+
# Pretend the whole test is wrapped in a context, so we can always code as if tests are in contexts.
|
16
|
+
@contexts = [Context.new("")]
|
17
|
+
@context_for_test = {}
|
18
|
+
|
19
|
+
# The tests defined in this test case. MiniTest::Unit::TestCase sorts these methods randomly or
|
20
|
+
# alphabetically. Let's run them in the order they were defined, as that's least surprising.
|
21
|
+
def test_methods()
|
22
|
+
tests = []
|
23
|
+
stack = [@contexts.first]
|
24
|
+
until stack.empty? do
|
25
|
+
item = stack.pop
|
26
|
+
stack += item.tests_and_subcontexts.reverse if item.is_a?(Context)
|
27
|
+
tests << item if item.is_a?(String)
|
28
|
+
end
|
29
|
+
tests
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.context(name, &block)
|
35
|
+
parent = @contexts.last
|
36
|
+
new_context = Context.new(name, parent)
|
37
|
+
parent.tests_and_subcontexts << new_context
|
38
|
+
@contexts << new_context
|
39
|
+
block.call
|
40
|
+
@contexts.pop
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.should(name, &block)
|
44
|
+
# When focus_enabled is true, we'll only be running the next should() block that gets defined.
|
45
|
+
if @focus_enabled
|
46
|
+
return unless @focus_next_test
|
47
|
+
@focus_next_test = false
|
48
|
+
end
|
49
|
+
|
50
|
+
context_name = @contexts[1..-1].map(&:name).join(" ")
|
51
|
+
context_name += " " unless context_name.empty?
|
52
|
+
test_method_name = "#{context_name}should #{name}"
|
53
|
+
define_method test_method_name, block
|
54
|
+
@contexts.last.tests_and_subcontexts << test_method_name
|
55
|
+
@context_for_test[test_method_name] = @contexts.last
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.setup(&block) @contexts.last.add_setup(&block) end
|
59
|
+
def self.teardown(&block) @contexts.last.add_teardown(&block) end
|
60
|
+
|
61
|
+
# setup_once blocks are run just once for a context, and not on a per-test basis. They are useful
|
62
|
+
# for integration tests with costly setup.
|
63
|
+
def self.setup_once(&block) @contexts.last.add_setup_once(&block) end
|
64
|
+
def self.teardown_once(&block) @contexts.last.add_teardown_once(&block) end
|
65
|
+
|
66
|
+
# "Focuses" the next test that's defined after this method is called, ensuring that only that test is run.
|
67
|
+
def self.focus
|
68
|
+
# Since we're focusing only the next test, remove any tests which were already defined.
|
69
|
+
context_for_test.values.uniq.each do |context|
|
70
|
+
context.tests_and_subcontexts.reject! { |test| test.is_a?(String) }
|
71
|
+
end
|
72
|
+
@focus_enabled = true
|
73
|
+
@focus_next_test = true
|
74
|
+
end
|
75
|
+
|
76
|
+
# run() is called by the MiniTest framework. This TestCase class is instantiated once per test method
|
77
|
+
# defined, and then run() is called on each test case instance.
|
78
|
+
def run(test_runner)
|
79
|
+
test_name = self.__name__
|
80
|
+
context = self.class.context_for_test[test_name]
|
81
|
+
result = nil
|
82
|
+
# Unit::TestCase's implementation of run() invokes the test method (test_name) with exception handling.
|
83
|
+
context.run_setup_and_teardown(test_name) { result = super }
|
84
|
+
result
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# A context keeps track of the tests defined inside of it as well as its setup and teardown blocks.
|
89
|
+
class Context
|
90
|
+
attr_reader :name, :parent_context
|
91
|
+
# We keep both tests and subcontexts in the same array because we need to know what the very last thing
|
92
|
+
# to execute inside of this context is, for the purpose of calling teardown_once at the correct time.
|
93
|
+
attr_accessor :tests_and_subcontexts
|
94
|
+
|
95
|
+
def initialize(name, parent_context = nil)
|
96
|
+
@name = name
|
97
|
+
@parent_context = parent_context
|
98
|
+
self.tests_and_subcontexts = []
|
99
|
+
end
|
100
|
+
|
101
|
+
# Runs the setup work for this context and any parent contexts, yields to the block (which should invoke
|
102
|
+
# the actual test method), and then completes the teardown work.
|
103
|
+
def run_setup_and_teardown(test_name)
|
104
|
+
contexts = ([self] + self.ancestor_contexts).reverse
|
105
|
+
contexts.each(&:setup_once)
|
106
|
+
contexts.each(&:setup)
|
107
|
+
yield
|
108
|
+
contexts.reverse!
|
109
|
+
contexts.each(&:teardown)
|
110
|
+
|
111
|
+
# If this is the last context being run in any parent contexts, run their teardown_once blocks.
|
112
|
+
if tests_and_subcontexts.last == test_name
|
113
|
+
self.teardown_once
|
114
|
+
descendant_context = nil
|
115
|
+
contexts.each do |ancestor|
|
116
|
+
break unless ancestor.tests_and_subcontexts.last == descendant_context
|
117
|
+
ancestor.teardown_once
|
118
|
+
descendant_context = ancestor
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def ancestor_contexts
|
124
|
+
ancestors = []
|
125
|
+
parent = self
|
126
|
+
ancestors << parent while (parent = parent.parent_context)
|
127
|
+
ancestors
|
128
|
+
end
|
129
|
+
|
130
|
+
def add_setup(&block) @setup = block end
|
131
|
+
def add_teardown(&block) @teardown = block end
|
132
|
+
def add_setup_once(&block) @setup_once = run_only_once(&block) end
|
133
|
+
def add_teardown_once(&block) @teardown_once = run_only_once(&block) end
|
134
|
+
|
135
|
+
def setup() @setup.call if @setup end
|
136
|
+
def teardown() @teardown.call if @teardown end
|
137
|
+
def setup_once() @setup_once.call if @setup_once end
|
138
|
+
def teardown_once() @teardown_once.call if @teardown_once end
|
139
|
+
|
140
|
+
private
|
141
|
+
def run_only_once(&block)
|
142
|
+
has_run = false
|
143
|
+
Proc.new { block.call unless has_run; has_run = true }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/scope.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "scope"
|
3
|
+
s.version = "0.1.1"
|
4
|
+
|
5
|
+
s.required_rubygems_version = Gem::Requirement.new(">=0") if s.respond_to? :required_rubygems_version=
|
6
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
7
|
+
|
8
|
+
s.author = "Phil Crosby"
|
9
|
+
s.email = "phil.crosby@gmail.com"
|
10
|
+
|
11
|
+
s.description = "Concise unit testing in the spirit of Shoulda"
|
12
|
+
s.summary = "Concise unit testing in the spirit of Shoulda"
|
13
|
+
s.homepage = "http://github.com/ooyala/scope"
|
14
|
+
s.rubyforge_project = "scope"
|
15
|
+
|
16
|
+
s.files = %w(
|
17
|
+
scope.gemspec
|
18
|
+
lib/scope.rb
|
19
|
+
)
|
20
|
+
s.add_dependency("minitest")
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scope
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Phil Crosby
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-05-22 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: minitest
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
description: Concise unit testing in the spirit of Shoulda
|
36
|
+
email: phil.crosby@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- scope.gemspec
|
45
|
+
- lib/scope.rb
|
46
|
+
has_rdoc: true
|
47
|
+
homepage: http://github.com/ooyala/scope
|
48
|
+
licenses: []
|
49
|
+
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
hash: 3
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project: scope
|
76
|
+
rubygems_version: 1.6.2
|
77
|
+
signing_key:
|
78
|
+
specification_version: 2
|
79
|
+
summary: Concise unit testing in the spirit of Shoulda
|
80
|
+
test_files: []
|
81
|
+
|