scope 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|
+
|