coulda 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY CHANGED
@@ -27,3 +27,9 @@ Cleanup
27
27
  - Minor refactorings
28
28
  - Added more examples
29
29
  - Adds "And" support to Given, When, and Then
30
+
31
+ 0.4.0
32
+ -----
33
+ - Complete internal redesign/rewrite
34
+ - Feature method now accepts a :testcase_class => Class argument specifying the subclass of Test::Unit::TestCase,
35
+ allowing for simple integration with other Test::Unit frameworks
@@ -1,9 +1,9 @@
1
- coulda
1
+ Coulda
2
2
  ------
3
- coulda gives you Cucumber-like BDD power but with an internal DSL. This means no "call-by-regexp" and
4
- no Gherkin/plain-text/external DSL. coulda is just code -- but with a Cucumber-like take on BDD.
3
+ Coulda gives you Cucumber-like BDD power but with an internal DSL. This means no "call-by-regexp" and
4
+ no Gherkin/plain-text/external DSL. Coulda is just code -- but with a Cucumber-like take on BDD.
5
5
 
6
- coulda, obviously was inspired by Cucumber, Shoulda, but also Thor. coulda believes that the best way to reuse
6
+ Coulda, obviously was inspired by Cucumber, Shoulda, but also Thor. Coulda believes that the best way to reuse
7
7
  code is the good ol' fashioned method. Instead of sharing files of regexps mapped to procs, you just write
8
8
  methods. That simple.
9
9
 
@@ -11,6 +11,10 @@ You can define the implementation of your Given/When/Then right there with the b
11
11
  if you want to reuse some Givens/Whens/Thens, just write a method! And if you find your Feature is getting
12
12
  too bloated or that you would like to reuse Givens/Whens/Thens between features, put them in a helper.
13
13
 
14
+ Coulda also easily supports your Test::Unit framework of choice (including Rails Tests).
15
+
16
+ See http://coulda.tiggerpalace.com for more information.
17
+
14
18
  Easy as pie.
15
19
 
16
20
  require 'rubygems'
@@ -46,3 +50,9 @@ Easy as pie.
46
50
  Then "blech" do; end
47
51
  end
48
52
  end
53
+
54
+ Thanks to
55
+ ---------
56
+ * David Chelimsky and Jim Weirich for turning me into the test-obsessed developer that I am.
57
+ * Timothy King for putting up with my scotch-powered rambling while working on v0.4.0
58
+ * We Are Titans (http://www.wearetitans.net/) for sponsoring v0.4.0
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'rake/gempackagetask'
6
6
 
7
7
  require 'lib/coulda/tasks'
8
8
 
9
- gem 'thoughtbot-shoulda'
9
+ gem 'shoulda'
10
10
  require 'shoulda'
11
11
 
12
12
  # Test::Unit::UI::VERBOSE
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{coulda}
8
- s.version = "0.3.1"
8
+ s.version = "0.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Evan David Light"]
12
- s.date = %q{2009-09-23}
12
+ s.date = %q{2009-11-23}
13
13
  s.description = %q{Behaviour Driven Development derived from Cucumber but as an internal DSL with methods for reuse}
14
14
  s.email = %q{evan@tiggerpalace.com}
15
15
  s.extra_rdoc_files = [
@@ -2,7 +2,6 @@ module Coulda
2
2
  SyntaxError = Class.new(StandardError)
3
3
  end
4
4
 
5
-
6
5
  require 'test/unit'
7
6
 
8
7
  gem 'jeremymcanally-pending', '>= 0.1'
@@ -12,56 +11,13 @@ require 'coulda/feature'
12
11
  require 'coulda/scenario'
13
12
 
14
13
  module Kernel
15
- def Feature(name, &block)
16
- f = Feature.for_name(name)
17
- f.class_eval(&block) if block_given?
18
- f.assert_description
14
+ def Feature(name, opts = {}, &block)
15
+ f = Feature.new(name, opts)
16
+ f.instance_eval &block if block_given?
17
+ f.assert_presence_of_intent
19
18
  f
20
19
  end
21
20
  end
22
21
 
23
- # Slightly tweeked version of #constantize from ActiveSupport 2.3.3
24
- class String
25
- # Ruby 1.9 introduces an inherit argument for Module#const_get and
26
- # #const_defined? and changes their default behavior.
27
- if Module.method(:const_get).arity == 1
28
- # Tries to find a constant with the name specified in the argument string:
29
- #
30
- # "Module".constantize # => Module
31
- # "Test::Unit".constantize # => Test::Unit
32
- #
33
- # The name is assumed to be the one of a top-level constant, no matter whether
34
- # it starts with "::" or not. No lexical context is taken into account:
35
- #
36
- # C = 'outside'
37
- # module M
38
- # C = 'inside'
39
- # C # => 'inside'
40
- # "C".constantize # => 'outside', same as ::C
41
- # end
42
- #
43
- # NameError is raised when the name is not in CamelCase or the constant is
44
- # unknown.
45
- def constantize
46
- names = self.split('::')
47
- names.shift if names.empty? || names.first.empty?
48
-
49
- constant = Object
50
- names.each do |name|
51
- constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
52
- end
53
- constant
54
- end
55
- else
56
- def constantize #:nodoc:
57
- names = self.split('::')
58
- names.shift if names.empty? || names.first.empty?
59
-
60
- constant = Object
61
- names.each do |name|
62
- constant = constant.const_get(name, false) || constant.const_missing(name)
63
- end
64
- constant
65
- end
66
- end
67
- end
22
+ require 'vendor/constantize'
23
+ require 'vendor/underscore'
@@ -1,119 +1,57 @@
1
1
  module Coulda
2
- Statement = Struct.new(:type, :name, :block)
2
+ # The Feature class is composed of an intent and a series of zero or more Scenarios that describe the behavior
3
+ # that satisfies the intent. Capturing intent is a key feature of Coulda. Intent defines the business value
4
+ # the Feature is attempting to fulfill. It serves as a reminder for developers (and customers) as to why this
5
+ # Feature was implemented and why this spec was written.
6
+ class Feature
7
+ include Test::Unit::Assertions
8
+
9
+ attr_reader :scenarios, :name
10
+ attr_accessor :current_scenario
11
+
12
+ def initialize(name, opts = {})
13
+ @name = name
14
+ @scenarios = []
15
+ Scenario.testcase_class = opts[:testcase_class] if opts[:testcase_class]
16
+ end
3
17
 
4
- module FeatureBehavior
5
- %w[Given When Then And].each do |statement|
18
+ %w[in_order_to as_a i_want_to].each do |intent|
6
19
  eval <<-HERE
7
- def #{statement}(name, &block)
8
- @pending = true unless block_given?
9
- @statements << stmt = Statement.new(:#{statement}, name, block)
20
+ # An intent specifier
21
+ def #{intent}(val = nil)
22
+ val ? @#{intent} = val : @#{intent}
10
23
  end
11
24
  HERE
12
25
  end
13
- end
14
-
15
- class Feature < Test::Unit::TestCase
16
- class << self
17
- attr_accessor :description
18
- attr_reader :in_order_to, :as_a, :i_want_to
19
-
20
- def include(*args)
21
- super *args
22
- extend *args
23
- end
24
-
25
- [:in_order_to, :as_a, :i_want_to].each do |field|
26
- eval <<-HERE
27
- def #{field}(arg = nil)
28
- if arg
29
- @#{field} = arg
30
- else
31
- @#{field}
32
- end
33
- end
34
- HERE
35
- end
36
-
37
- def for_name(name)
38
- klass = Class.new(Feature)
39
- klass.extend(FeatureBehavior)
40
- klass.description = name
41
- class_name = feature_name_from(name)
42
- Object.const_set(class_name, klass)
43
- klass
44
- end
45
-
46
- def feature_name_from(name)
47
- "Feature" + name.split(/\s|_/).map { |w| w.capitalize }.join
48
- end
49
-
50
- def assert_description
51
- if @in_order_to || @as_a || @i_want_to
52
- raise SyntaxError.new("Must call in_order_to if as_a and/or i_wanted_to called") unless @in_order_to
53
- raise SyntaxError.new("Must call as_a if in_order_to and/or i_want_to called") unless @as_a
54
- raise SyntaxError.new("Must call i_want_to if in_order_to and/or as_a called") unless @i_want_to
55
- end
56
- end
57
-
58
- def Scenario(name, &test_implementation)
59
- raise SyntaxError.new("A scenario requires a name") unless name
60
- create_test_method_for(name, &test_implementation)
61
- create_scenario_for(name, &test_implementation)
62
- end
63
-
64
- def scenarios
65
- @scenarios ||= []
66
- end
67
-
68
- def pending?
69
- @scenarios.all? { |s| !s.pending? }
70
- end
71
26
 
72
- private
73
-
74
- def create_test_method_for(name, &test_implementation)
75
- method_name = "test_#{name.sub(/\s+/, "_").downcase}"
76
- @scenarios ||= []
77
- if block_given?
78
- define_method(method_name, &test_implementation)
79
- else
80
- define_method(method_name) { pending }
81
- end
82
- end
83
-
84
- def create_scenario_for(name, &test_implementation)
85
- @scenarios ||= []
86
- scenario = nil
87
- if block_given?
88
- scenario = Scenario.new(name, &test_implementation)
89
- scenario.statements = extract_statements_from &test_implementation
90
- else
91
- scenario = Scenario.new(name)
27
+ %w[Given When Then And].each do |stmt|
28
+ eval <<-HERE
29
+ # Specifies a prereqisite, event, or expectation. May be used in any order within the spec
30
+ def #{stmt}(text, &block)
31
+ @current_scenario.statements << { :type => :#{stmt}, :text => text }
32
+ @current_scenario.steps << block if block
92
33
  end
93
- @scenarios << scenario
94
- scenario
95
- end
34
+ HERE
35
+ end
96
36
 
97
- def extract_statements_from(&test_implementation)
98
- @statements ||= []
99
- self.instance_eval(&test_implementation)
100
- statements = @statements
101
- @statements = []
102
- statements
103
- end
37
+ # Creates a Scenario instance and adds it to the Feature
38
+ def Scenario(scenario_name, &block)
39
+ @scenarios << scenario = Scenario.new(scenario_name, self, &block)
40
+ scenario
104
41
  end
105
42
 
106
- # Scenario-less features should be pending
107
- def default_test
108
- pending
43
+ # Accessor to the Scenario-created TestCases
44
+ def tests
45
+ @scenarios.collect { |s| s.test_class }
109
46
  end
110
47
 
111
- %w[Given When Then And].each do |statement|
112
- eval <<-HERE
113
- def #{statement}(name, &block)
114
- yield if block_given?
115
- end
116
- HERE
48
+
49
+ # Raises an error if only some of the intent is captured (i.e., in_order_to and as_a without i_want_to)
50
+ def assert_presence_of_intent
51
+ presence = %w[in_order_to as_a i_want_to].map { |intent| instance_variable_get("@#{intent}") }
52
+ if presence.any? { |p| p } && !presence.all? { |p| p }
53
+ raise SyntaxError.new("Must have all or none of in_order, as_a, and i_want_to called in a Feature")
54
+ end
117
55
  end
118
56
  end
119
57
  end
@@ -1,17 +1,74 @@
1
1
  module Coulda
2
+ # A factory for Test::Unit::TestCases (or their subclass).
2
3
  class Scenario
3
- attr_reader :name
4
- attr_accessor :statements
4
+ attr_reader :name, :test_class
5
+ attr_accessor :steps, :statements
5
6
 
6
- def initialize(name, &block)
7
- @name = name.to_s
8
- @block = block
7
+ # Class-level method to set the subclass of Test::Unit::TestCase to use as the parent for
8
+ # tests manufactured by Scenarios.
9
+ def self.testcase_class=(klass)
10
+ unless klass.ancestors.include?(Test::Unit::TestCase)
11
+ raise Exception.new("class must inherit from Test::Unit:TestCase")
12
+ end
13
+ @testcase_class = klass
14
+ end
15
+
16
+ def self.testcase_class
17
+ @testcase_class
18
+ end
19
+
20
+ def initialize(name, my_feature, &block)
21
+ raise Exception.new("Scenario must have a name") unless name
22
+ @name = name
9
23
  @statements = []
10
- @pending = !block_given?
24
+ @steps = []
25
+ @pending = false
26
+ @test_class = ::Class.new(Scenario.testcase_class || Test::Unit::TestCase)
27
+ assign_test_class_to_const(my_feature)
28
+ test_impl = nil
29
+ if block
30
+ create_and_provision_test_method_for my_feature, &block
31
+ else
32
+ @pending = true
33
+ define_test_method_using { pending }
34
+ end
11
35
  end
12
36
 
37
+ # Predicate indicating if the Scenario was provided with an example
13
38
  def pending?
14
39
  @pending
15
40
  end
41
+
42
+ private
43
+
44
+ def create_and_provision_test_method_for(feature, &block)
45
+ execute block, :within => feature
46
+ inject_test_steps_into @test_class
47
+ define_test_method_using { self.class.test_steps.each { |s| feature.instance_eval &s } }
48
+ end
49
+
50
+ def assign_test_class_to_const(my_feature)
51
+ base_name = "#{my_feature.name}_#{@name}_#{rand(1_000_000_000)}"
52
+ base_name = "letter_" + base_name if base_name =~ /^[^a-zA-Z]/
53
+ titleized_underscored_name = base_name.super_custom_underscore.gsub(/\b('?[a-z])/) { $1.upcase }
54
+ ::Module.const_set(titleized_underscored_name, @test_class)
55
+ end
56
+
57
+ def define_test_method_using(&block)
58
+ @test_class.send(:define_method, "test_#{@name.downcase.super_custom_underscore}", &block)
59
+ end
60
+
61
+ def execute(block, params = {})
62
+ feature = params[:within]
63
+ feature.current_scenario = self
64
+ feature.instance_eval &block
65
+ end
66
+
67
+ def inject_test_steps_into(test_class)
68
+ class << test_class
69
+ attr_accessor :test_steps
70
+ end
71
+ test_class.test_steps = @steps
72
+ end
16
73
  end
17
74
  end
@@ -1,41 +1,23 @@
1
1
  require File.join(File.dirname(__FILE__), "test_helper")
2
2
 
3
3
  class FeatureTest < Test::Unit::TestCase
4
- context "A Feature instance" do
5
- @@counter = 1
4
+ context "A Feature" do
6
5
  setup do
7
- feature_class = Feature.for_name "foobarblech#{@@counter}"
8
- @@counter += 1
9
- @feature = feature_class.new("default_test")
10
- end
11
-
12
- %w[Given When Then And].each do |condition|
13
- should "have a method called '#{condition}'" do
14
- assert(@feature.respond_to?(condition))
6
+ @feature = Feature "foobarblech#{@@counter}" do
7
+ Scenario "" do
8
+ Given "" do; end
9
+ Then "" do; end
10
+ end
15
11
  end
16
12
  end
17
- end
18
13
 
19
- context "A Feature class" do
20
- should "have Test::Unit::TestCase as an ancestor" do
21
- assert(Feature.ancestors.include?(Test::Unit::TestCase))
14
+ should "contain tests" do
15
+ assert(@feature.tests.first)
22
16
  end
23
17
 
24
- context "created by name" do
25
- @@counter = 1
26
- setup do
27
- @feature = Feature.for_name "foo#{@@counter}"
28
- @@counter += 1
29
- end
30
-
31
- should "be a subclass of Feature" do
32
- assert(@feature.ancestors.include?(Feature))
33
- end
34
-
35
- %w[Given When Then And].each do |condition|
36
- should "have a method called '#{condition}'" do
37
- assert(@feature.respond_to?(condition), "does not have a method called #{condition}")
38
- end
18
+ %w[Given When Then And].each do |condition|
19
+ should "have a method called '#{condition}'" do
20
+ assert(@feature.respond_to?(condition))
39
21
  end
40
22
  end
41
23
 
@@ -93,9 +75,8 @@ class FeatureTest < Test::Unit::TestCase
93
75
  end
94
76
  end
95
77
 
96
- should "not have any errors when run" do
97
- result = run_feature @feature_without_scenarios
98
- Object::RUBY_VERSION =~ /^1.9/ ? assert(result) : assert(result.passed?)
78
+ should "not have any test" do
79
+ assert(@feature_without_scenarios.tests.empty?)
99
80
  end
100
81
  end
101
82
 
@@ -113,11 +94,11 @@ class FeatureTest < Test::Unit::TestCase
113
94
  ### Integration tests
114
95
 
115
96
  context "with a block containing a scenario" do
116
- should "create a Feature instance method named 'test_<underscored_scenario_name>'" do
117
- @feature_without_errors.Scenario "pending scenario"
97
+ should "create a Feature instance method named 'test_<underscored_feature_name>_<underscored_scenario_name>'" do
98
+ @feature_without_errors.Scenario("pending scenario") {}
118
99
  test_name = "test_pending_scenario"
119
- test_name = :test_pending_scenario if RUBY_VERSION =~ /^1.9/
120
- assert(@feature_without_errors.instance_methods.include?(test_name), "Feature is missing test method from scenario")
100
+ test_name = test_name.to_sym if RUBY_VERSION =~ /^1.9/
101
+ assert(@feature_without_errors.tests.first.instance_methods.include?(test_name), "Test is missing test method from scenario")
121
102
  end
122
103
 
123
104
  should "create a Scenario" do
@@ -1,6 +1,6 @@
1
1
  require File.join(File.dirname(__FILE__), "..", "test_helper")
2
2
 
3
- Feature "Using Coulda" do
3
+ Feature "Using Coulda", :testcase_class => Test::Unit::TestCase do
4
4
  in_order_to "perform lightweight integration/acceptance testing with Coulda"
5
5
  as_a "developer"
6
6
  i_want_to "have typical Coulda usage work"
@@ -13,7 +13,7 @@ class Issue1Test < Test::Unit::TestCase
13
13
  pendings_are_errors
14
14
 
15
15
  @feature_without_errors = Feature "Issue 1 feature" do
16
- include GivenSomething
16
+ extend GivenSomething
17
17
 
18
18
  in_order_to "foo"
19
19
  as_a "bar"
@@ -28,7 +28,7 @@ class Issue1Test < Test::Unit::TestCase
28
28
  end
29
29
 
30
30
  should "pass" do
31
- assert(run_feature(@feature_without_errors))
31
+ assert(run_feature(@feature_without_errors.tests.first))
32
32
  end
33
33
  end
34
34
  end
@@ -3,13 +3,13 @@ require File.join(File.dirname(__FILE__), "test_helper")
3
3
  class ScenarioTest < Test::Unit::TestCase
4
4
  context "A Scenario" do
5
5
  setup do
6
- @scenario = Scenario.new("foobar")
6
+ @scenario = Scenario.new("foobar", Feature("something_or_other")) {}
7
7
  end
8
8
 
9
9
  context "when instantiated" do
10
10
  context "with only a String" do
11
11
  setup do
12
- @scenario = Scenario.new("foobar")
12
+ @scenario = Scenario.new("foobar", Feature("another"))
13
13
  end
14
14
 
15
15
  should "be pending" do
@@ -18,5 +18,15 @@ class ScenarioTest < Test::Unit::TestCase
18
18
  end
19
19
  end
20
20
  end
21
+
22
+ should "allow Test::Unit::TestCase class to subclass to be set" do
23
+ Scenario.testcase_class = Test::Unit::TestCase
24
+ end
25
+
26
+ should "raise an Exception if provided a class that does not inherit from Test::Unit::TestCase" do
27
+ assert_raises Exception do
28
+ Scenario.testcase_class = Object
29
+ end
30
+ end
21
31
  end
22
32
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coulda
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan David Light
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-23 00:00:00 -04:00
12
+ date: 2009-11-23 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency