scope 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/scope.rb +146 -0
  2. data/scope.gemspec +21 -0
  3. 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
+