rspec 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/CHANGES +28 -3
  2. data/EXAMPLES.rd +2 -16
  3. data/README +5 -2
  4. data/Rakefile +8 -4
  5. data/bin/spec +1 -0
  6. data/examples/custom_formatter.rb +2 -1
  7. data/examples/helper_method_spec.rb +12 -0
  8. data/examples/{mocking_spec.rb → mocking_example.rb} +0 -0
  9. data/examples/{bdd_framework_spec.rb → predicate_example.rb} +0 -0
  10. data/examples/stubbing_example.rb +28 -0
  11. data/lib/spec.rb +2 -1
  12. data/lib/spec/expectations.rb +7 -0
  13. data/lib/spec/expectations/diff.rb +56 -0
  14. data/lib/spec/expectations/exceptions.rb +6 -0
  15. data/lib/spec/expectations/expectations.rb +19 -0
  16. data/lib/spec/expectations/have_helper.rb +41 -0
  17. data/lib/spec/expectations/helper.rb +4 -0
  18. data/lib/spec/expectations/should_base.rb +52 -0
  19. data/lib/spec/expectations/should_helper.rb +93 -0
  20. data/lib/spec/expectations/should_negator.rb +71 -0
  21. data/lib/spec/{api → expectations}/sugar.rb +8 -8
  22. data/lib/spec/mocks.rb +5 -0
  23. data/lib/spec/{api/mocks → mocks}/argument_expectation.rb +1 -1
  24. data/lib/spec/{api → mocks}/exceptions.rb +3 -5
  25. data/lib/spec/{api/mocks → mocks}/message_expectation.rb +15 -9
  26. data/lib/spec/{api/mocks → mocks}/mock.rb +44 -26
  27. data/lib/spec/{api/mocks → mocks}/order_group.rb +1 -1
  28. data/lib/spec/runner/backtrace_tweaker.rb +4 -1
  29. data/lib/spec/runner/execution_context.rb +13 -4
  30. data/lib/spec/runner/formatter/base_text_formatter.rb +26 -13
  31. data/lib/spec/runner/formatter/html_formatter.rb +1 -1
  32. data/lib/spec/runner/option_parser.rb +6 -2
  33. data/lib/spec/runner/specification.rb +2 -2
  34. data/lib/spec/test_to_spec/ruby2ruby.rb +1 -1
  35. data/lib/spec/version.rb +1 -1
  36. data/test/spec/expectations/arbitrary_operator_test.rb +55 -0
  37. data/test/spec/expectations/arbitrary_predicate_test.rb +163 -0
  38. data/test/spec/{api/helper → expectations}/containment_test.rb +2 -2
  39. data/test/spec/expectations/diff_test.rb +62 -0
  40. data/test/spec/{api/helper → expectations}/identity_test.rb +2 -2
  41. data/test/spec/{api/helper → expectations}/object_equality_test.rb +2 -2
  42. data/test/spec/{api/helper → expectations}/raising_test.rb +5 -5
  43. data/test/spec/{api/helper → expectations}/regex_matching_test.rb +6 -6
  44. data/test/spec/{api/helper → expectations}/should_have_test.rb +11 -2
  45. data/test/spec/{api/helper → expectations}/should_satisfy_test.rb +2 -4
  46. data/test/spec/{api → expectations}/sugar_test.rb +8 -8
  47. data/test/spec/expectations/supported_symbols_test.rb +33 -0
  48. data/test/spec/{api/helper → expectations}/throwing_test.rb +2 -2
  49. data/test/spec/{api/helper → expectations}/true_false_special_case_test.rb +2 -2
  50. data/test/spec/{api/helper → expectations}/typing_test.rb +2 -2
  51. data/test/spec/{api/mocks → mocks}/mock_arg_constraints_test.rb +4 -4
  52. data/test/spec/{api/mocks → mocks}/mock_counts_test.rb +2 -2
  53. data/test/spec/{api/mocks → mocks}/mock_ordering_test.rb +2 -2
  54. data/test/spec/{api/mocks → mocks}/mock_test.rb +22 -7
  55. data/test/spec/{api/mocks → mocks}/null_object_test.rb +8 -2
  56. data/test/spec/runner/backtrace_tweaker_test.rb +21 -19
  57. data/test/spec/runner/context_matching_test.rb +2 -2
  58. data/test/spec/runner/context_runner_test.rb +6 -6
  59. data/test/spec/runner/context_test.rb +1 -1
  60. data/test/spec/runner/execution_context_test.rb +22 -5
  61. data/test/spec/runner/formatter/failure_dump_test.rb +7 -7
  62. data/test/spec/runner/option_parser_test.rb +20 -0
  63. data/test/spec/runner/reporter_test.rb +3 -3
  64. data/test/spec/runner/specification_test.rb +3 -3
  65. data/test/spec/test_to_spec/sexp_transformer_assertion_test.rb +4 -4
  66. data/test/spec/test_to_spec/sexp_transformer_test.rb +1 -1
  67. data/test/spec/test_to_spec/testfiles/test_unit_api_test.rb +2 -2
  68. data/test/test_classes.rb +21 -1
  69. data/test/test_helper.rb +1 -1
  70. data/vendor/selenium/README.txt +23 -0
  71. data/vendor/selenium/find_rspecs_home_page.rb +23 -0
  72. data/vendor/selenium/rspec_selenium.rb +33 -0
  73. data/vendor/watir/README.txt +32 -0
  74. data/vendor/watir/find_rspecs_home_page.rb +21 -0
  75. data/vendor/watir/find_rspecs_home_page.txt +15 -0
  76. data/vendor/watir/rspec_watir.rb +45 -0
  77. metadata +52 -41
  78. data/examples/airport_spec.rb +0 -33
  79. data/examples/custom_method_spec.rb +0 -24
  80. data/examples/sugar_spec.rb +0 -14
  81. data/lib/spec/api.rb +0 -8
  82. data/lib/spec/api/expectations.rb +0 -17
  83. data/lib/spec/api/helper.rb +0 -4
  84. data/lib/spec/api/helper/diff.rb +0 -54
  85. data/lib/spec/api/helper/have_helper.rb +0 -40
  86. data/lib/spec/api/helper/should_base.rb +0 -31
  87. data/lib/spec/api/helper/should_helper.rb +0 -93
  88. data/lib/spec/api/helper/should_negator.rb +0 -72
  89. data/test/spec/api/helper/arbitrary_predicate_test.rb +0 -112
  90. data/test/spec/api/helper/diff_test.rb +0 -60
@@ -0,0 +1,71 @@
1
+ module Spec
2
+ module Expectations
3
+ class ShouldNegator < ShouldBase
4
+
5
+ def initialize(target)
6
+ @target = target
7
+ @be_seen = false
8
+ end
9
+
10
+ def satisfy
11
+ fail_with_message "Supplied expectation was satisfied, but should not have been" if (yield @target)
12
+ end
13
+
14
+ def equal(expected)
15
+ fail_with_message(default_message("should not equal", expected)) if (@target == expected)
16
+ end
17
+
18
+ def be(expected = :no_arg)
19
+ @be_seen = true
20
+ return self if (expected == :no_arg)
21
+ fail_with_message(default_message("should not be", expected)) if (@target.equal?(expected))
22
+ end
23
+
24
+ def an_instance_of expected_class
25
+ fail_with_message(default_message("should not be an instance of", expected_class)) if @target.instance_of? expected_class
26
+ end
27
+
28
+ def a_kind_of expected_class
29
+ fail_with_message(default_message("should not be a kind of", expected_class)) if @target.kind_of? expected_class
30
+ end
31
+
32
+ def respond_to message
33
+ fail_with_message(default_message("should not respond to", message)) if @target.respond_to? message
34
+ end
35
+
36
+ def match(expected)
37
+ fail_with_message(default_message("should not match", expected)) if (@target =~ expected)
38
+ end
39
+
40
+ def raise(exception=Exception, message=nil)
41
+ begin
42
+ @target.call
43
+ rescue exception => e
44
+ return unless message.nil? || e.message == message
45
+ fail_with_message("#{default_message("should not raise", exception)}") if e.instance_of? exception
46
+ fail_with_message("#{default_message("should not raise", exception)} but raised #{e.inspect}") unless e.instance_of? exception
47
+ rescue
48
+ true
49
+ end
50
+ end
51
+
52
+ def throw(symbol=:___this_is_a_symbol_that_will_likely_never_occur___)
53
+ begin
54
+ catch symbol do
55
+ @target.call
56
+ return true
57
+ end
58
+ fail_with_message(default_message("should not throw", symbol.inspect))
59
+ rescue NameError
60
+ true
61
+ end
62
+ end
63
+
64
+ def method_missing(original_sym, *args)
65
+ actual_sym = find_supported_sym(original_sym)
66
+ return unless @target.__send__(actual_sym, *args)
67
+ fail_with_message(default_message("should not#{@be_seen ? ' be' : ''} #{original_sym}" + (args.empty? ? '' : (' ' + args.join(', ')))))
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,19 +1,19 @@
1
1
  module Spec
2
- module Api
2
+ module Expectations
3
3
  # This module adds syntactic sugar that allows usage of should_* instead of should.*
4
4
  module UnderscoreSugar
5
- module SugarizeForRspec; end
5
+ module SweetTooth; end
6
6
 
7
- def sugarize_for_rspec!
7
+ def handle_underscores_for_rspec!
8
8
  original_method_missing = instance_method(:method_missing)
9
9
  class_eval do
10
- include SugarizeForRspec # This is meant to add a signature to the object that sugarization occurred.
10
+ include SweetTooth # This is meant to add a signature to the object that sugarization occurred.
11
11
  def method_missing(sym, *args, &block)
12
12
  _method_missing(sym, args, block)
13
13
  end
14
14
 
15
15
  define_method :_method_missing do |sym, args, block|
16
- return original_method_missing.bind(self).call(sym, *args, &block) unless __is_sweetened?(sym)
16
+ return original_method_missing.bind(self).call(sym, *args, &block) unless __sweetened?(sym)
17
17
 
18
18
  object = self
19
19
  calls = sym.to_s.split("_")
@@ -27,7 +27,7 @@ module Spec
27
27
  return object.__send__(calls.join("_"), *args, &block)
28
28
  end
29
29
 
30
- def __is_sweetened?(sym) #:nodoc:
30
+ def __sweetened?(sym) #:nodoc:
31
31
  return true if sym.to_s =~ /^should_/
32
32
  end
33
33
  end
@@ -37,7 +37,7 @@ module Spec
37
37
  end
38
38
 
39
39
  class Module
40
- include Spec::Api::UnderscoreSugar
40
+ include Spec::Expectations::UnderscoreSugar
41
41
  end
42
42
 
43
- Object.sugarize_for_rspec!
43
+ Object.handle_underscores_for_rspec!
@@ -0,0 +1,5 @@
1
+ require 'spec/mocks/mock'
2
+ require 'spec/mocks/argument_expectation'
3
+ require 'spec/mocks/message_expectation'
4
+ require 'spec/mocks/order_group'
5
+ require 'spec/mocks/exceptions'
@@ -1,5 +1,5 @@
1
1
  module Spec
2
- module Api
2
+ module Mocks
3
3
 
4
4
  class LiteralArgConstraint
5
5
  def initialize(literal)
@@ -1,12 +1,10 @@
1
1
  module Spec
2
- module Api
3
- class ExpectationNotMetError < StandardError
4
- end
5
-
2
+ module Mocks
6
3
  class MockExpectationError < StandardError
7
4
  end
8
-
5
+
9
6
  class AmbiguousReturnError < StandardError
10
7
  end
11
8
  end
12
9
  end
10
+
@@ -1,17 +1,17 @@
1
1
  module Spec
2
- module Api
2
+ module Mocks
3
3
 
4
4
  # Represents the expection of the reception of a message
5
5
  class MessageExpectation
6
6
 
7
- def initialize(mock_name, expectation_ordering, expected_from, sym, method_block)
7
+ def initialize(mock_name, expectation_ordering, expected_from, sym, method_block, expected_received_count=1)
8
8
  @mock_name = mock_name
9
9
  @expected_from = expected_from
10
10
  @sym = sym
11
11
  @method_block = method_block
12
12
  @return_block = lambda {}
13
13
  @received_count = 0
14
- @expected_received_count = 1
14
+ @expected_received_count = expected_received_count
15
15
  @args_expectation = ArgumentExpectation.new([:any_args])
16
16
  @consecutive = false
17
17
  @exception_to_raise = nil
@@ -53,7 +53,7 @@ module Spec
53
53
 
54
54
  message = "Mock '#{@mock_name}' expected '#{@sym}' #{count_message}, but received it #{@received_count} times"
55
55
  begin
56
- Kernel::raise(Spec::Api::MockExpectationError, message)
56
+ Kernel::raise(Spec::Mocks::MockExpectationError, message)
57
57
  rescue => error
58
58
  error.backtrace.insert(0, @expected_from)
59
59
  Kernel::raise error
@@ -64,7 +64,7 @@ module Spec
64
64
  return unless @ordered
65
65
  return @ordering.consume(self) if @ordering.ready_for?(self)
66
66
  message = "Mock '#{@mock_name}' received '#{@sym}' out of order"
67
- Kernel::raise(Spec::Api::MockExpectationError, message)
67
+ Kernel::raise(Spec::Mocks::MockExpectationError, message)
68
68
  end
69
69
 
70
70
  # This method is called when a method is invoked on a mock
@@ -91,17 +91,17 @@ module Spec
91
91
  def invoke_method_block(args)
92
92
  begin
93
93
  @method_block.call(*args)
94
- rescue Spec::Api::ExpectationNotMetError => detail
95
- Kernel::raise Spec::Api::MockExpectationError, "Call expectation violated with: " + detail
94
+ rescue Spec::Expectations::ExpectationNotMetError => detail
95
+ Kernel::raise Spec::Mocks::MockExpectationError, "Call expectation violated with: " + detail
96
96
  end
97
97
  end
98
98
 
99
99
  def invoke_with_yield(block)
100
100
  if block.nil?
101
- Kernel::raise Spec::Api::MockExpectationError, "Expected block to be passed"
101
+ Kernel::raise Spec::Mocks::MockExpectationError, "Expected block to be passed"
102
102
  end
103
103
  if @args_to_yield.length != block.arity
104
- Kernel::raise Spec::Api::MockExpectationError, "Wrong arity of passed block. Expected #{@args_to_yield.size}"
104
+ Kernel::raise Spec::Mocks::MockExpectationError, "Wrong arity of passed block. Expected #{@args_to_yield.size}"
105
105
  end
106
106
  block.call(*@args_to_yield)
107
107
  end
@@ -196,5 +196,11 @@ module Spec
196
196
  self
197
197
  end
198
198
  end
199
+
200
+ class NegativeMessageExpectation < MessageExpectation
201
+ def initialize(mock_name, expectation_ordering, expected_from, sym, method_block)
202
+ super mock_name, expectation_ordering, expected_from, sym, method_block, 0
203
+ end
204
+ end
199
205
  end
200
206
  end
@@ -1,11 +1,6 @@
1
1
  module Spec
2
- module Api
3
- class Mock
4
- # Remove all methods so they can be mocked too
5
- (public_instance_methods - ['__id__', '__send__', 'nil?']).each do |m|
6
- undef_method m
7
- end
8
-
2
+ module Mocks
3
+ module MockInstanceMethods
9
4
  # Creates a new mock with a +name+ (that will be used in error messages only)
10
5
  # Options:
11
6
  # * <tt>:null_object</tt> - if true, the mock object acts as a forgiving null object allowing any message to be sent to it.
@@ -15,17 +10,15 @@ module Spec
15
10
  @expectations = []
16
11
  @expectation_ordering = OrderGroup.new
17
12
  end
18
-
13
+
19
14
  def should_receive(sym, &block)
20
- expected_from = caller(1)[0]
21
- expectation = MessageExpectation.new(@name, @expectation_ordering, expected_from, sym, block_given? ? block : nil)
22
- @expectations << expectation
23
- expectation
15
+ add MessageExpectation, caller(1)[0], sym, &block
24
16
  end
25
17
 
26
18
  def should_not_receive(sym, &block)
19
+ add NegativeMessageExpectation, caller(1)[0], sym, &block
27
20
  end
28
-
21
+
29
22
  def __verify #:nodoc:
30
23
  @expectations.each do |expectation|
31
24
  expectation.verify_messages_received
@@ -33,30 +26,55 @@ module Spec
33
26
  end
34
27
 
35
28
  def method_missing(sym, *args, &block)
36
- if expectation = find_matching_expectation(sym, *args)
37
- expectation.invoke(args, block)
38
- else
39
- begin
40
- # act as null object if method is missing and we ignore them. return value too!
41
- @options[:null_object] ? self : super(sym, *args, &block)
42
- rescue NoMethodError
43
- arg_message = args.collect{|arg| "<#{arg}:#{arg.class.name}>"}.join(", ")
44
- Kernel::raise Spec::Api::MockExpectationError, "Mock '#{@name}' received unexpected message '#{sym}' with [#{arg_message}]"
45
- end
29
+ begin
30
+ return self if @options[:null_object]
31
+ super(sym, *args, &block)
32
+ rescue NoMethodError
33
+ arg_message = args.collect{|arg| "<#{arg}:#{arg.class.name}>"}.join(", ")
34
+ Kernel::raise Spec::Mocks::MockExpectationError, "Mock '#{@name}' received unexpected message '#{sym}' with [#{arg_message}]"
46
35
  end
47
36
  end
48
-
37
+
49
38
  private
50
39
 
51
40
  DEFAULT_OPTIONS = {
52
41
  :null_object => false
53
42
  }
43
+
44
+ def add(expectation_class, expected_from, sym, &block)
45
+ define_expected_method(sym)
46
+ expectation = expectation_class.send(:new, @name, @expectation_ordering, expected_from, sym, block_given? ? block : nil)
47
+ @expectations << expectation
48
+ expectation
49
+ end
50
+
51
+ def metaclass
52
+ class << self; self; end
53
+ end
54
+
55
+ def define_expected_method(sym)
56
+ metaclass.__send__ :class_eval, %{
57
+ def #{sym}(*args, &block)
58
+ message_received :#{sym}, *args, &block # ?
59
+ end
60
+ }
61
+ end
62
+
63
+ def message_received(sym, *args, &block)
64
+ if expectation = find_matching_expectation(sym, *args)
65
+ expectation.invoke(args, block)
66
+ else
67
+ method_missing(sym, *args, &block)
68
+ end
69
+ end
54
70
 
55
71
  def find_matching_expectation(sym, *args)
56
72
  expectation = @expectations.find {|expectation| expectation.matches(sym, args)}
57
73
  end
58
-
59
74
  end
60
-
75
+
76
+ class Mock
77
+ include MockInstanceMethods
78
+ end
61
79
  end
62
80
  end
@@ -1,5 +1,5 @@
1
1
  module Spec
2
- module Api
2
+ module Mocks
3
3
  class OrderGroup
4
4
  def initialize
5
5
  @ordering = Array.new
@@ -25,8 +25,11 @@ module Spec
25
25
  error.backtrace.collect! do |line|
26
26
  line = tweak_instance_exec_line line, spec_name
27
27
  line = nil if line =~ /\/lib\/ruby\//
28
- line = nil if line =~ /\/lib\/spec\/api\//
28
+ line = nil if line =~ /\/lib\/spec\/expectations\//
29
+ line = nil if line =~ /\/lib\/spec\/mocks\//
30
+ line = nil if line =~ /\/lib\/spec\/rake\//
29
31
  line = nil if line =~ /\/lib\/spec\/runner\//
32
+ line = nil if line =~ /\/lib\/spec\/stubs\//
30
33
  line = nil if line =~ /bin\/spec:/
31
34
  line = nil if line =~ /lib\/rspec_on_rails/
32
35
  line = nil if line =~ /script\/rails_spec/
@@ -5,19 +5,28 @@ module Spec
5
5
  def initialize(spec)
6
6
  @spec = spec
7
7
  end
8
-
8
+
9
9
  def mock(name, options={})
10
- mock = Api::Mock.new(name, options)
10
+ mock = Spec::Mocks::Mock.new(name, options)
11
11
  @spec.add_mock(mock)
12
12
  mock
13
13
  end
14
14
 
15
+ def stub(object, name="")
16
+ stub_space.create_stub(object, name)
17
+ end
18
+
15
19
  def duck_type(*args)
16
- return Api::DuckTypeArgConstraint.new(*args)
20
+ return Spec::Mocks::DuckTypeArgConstraint.new(*args)
17
21
  end
18
22
 
19
23
  def violated(message="")
20
- raise Spec::Api::ExpectationNotMetError.new(message)
24
+ raise Spec::Expectations::ExpectationNotMetError.new(message)
25
+ end
26
+
27
+ private
28
+ def stub_space
29
+ @spec.stub_space
21
30
  end
22
31
  end
23
32
  include InstanceMethods
@@ -5,10 +5,16 @@ module Spec
5
5
  # non-text based ones too - just ignore the +output+ constructor
6
6
  # argument.
7
7
  class BaseTextFormatter
8
- def initialize(output, dry_run=false)
9
- @dry_run = dry_run
8
+ def initialize(output, dry_run=false, colour=false)
10
9
  @output = output
11
- end
10
+ @dry_run = dry_run
11
+ @colour = colour
12
+ begin
13
+ require 'Win32/Console/ANSI' if @colour && PLATFORM =~ /win32/
14
+ rescue LoadError
15
+ raise "You must gem install win32console to use --color on Windows"
16
+ end
17
+ end
12
18
 
13
19
  # This method is invoked before any specs are run, right after
14
20
  # they have all been collected. This can be useful for special
@@ -55,22 +61,29 @@ module Spec
55
61
  # of the associated spec. +failure+ is a Failure object, which contains detailed
56
62
  # information about the failure.
57
63
  def dump_failure(counter, failure)
58
- @output << "\n"
59
- @output << counter.to_s << ")\n"
60
- @output << "#{failure.header}\n"
61
- @output << "#{failure.message}\n"
62
- @output << "#{failure.backtrace}\n"
64
+ @output.puts
65
+ @output.puts "#{counter.to_s})"
66
+ @output.puts failure.header
67
+ @output.puts failure.message
68
+ @output.puts failure.backtrace
63
69
  @output.flush
64
70
  end
65
71
 
66
72
  # This method is invoked at the very end.
67
73
  def dump_summary(duration, spec_count, failure_count)
68
74
  return if @dry_run
69
- @output << "\n"
70
- @output << "Finished in " << (duration).to_s << " seconds\n\n"
71
- @output << "#{spec_count} specification#{'s' unless spec_count == 1}, "
72
- @output << "#{failure_count} failure#{'s' unless failure_count == 1}"
73
- @output << "\n"
75
+ if @colour && @output == STDOUT
76
+ colour_prefix = (failure_count == 0 ? "\e[32m" : "\e[31m")
77
+ colour_postfix = "\e[0m"
78
+ summary_output = Kernel
79
+ else
80
+ colour_prefix = colour_postfix = ""
81
+ summary_output = @output
82
+ end
83
+ @output.puts
84
+ @output.puts "Finished in #{duration} seconds"
85
+ @output.puts
86
+ summary_output.puts "#{colour_prefix}#{spec_count} specification#{'s' unless spec_count == 1}, #{failure_count} failure#{'s' unless failure_count == 1}#{colour_postfix}"
74
87
  @output.flush
75
88
  end
76
89
  end
@@ -2,7 +2,7 @@ module Spec
2
2
  module Runner
3
3
  module Formatter
4
4
  class HtmlFormatter < BaseTextFormatter
5
- def initialize(output, dry_run=false)
5
+ def initialize(output, dry_run=false, colour=false)
6
6
  super
7
7
  @current_count = 0
8
8
  end
@@ -8,7 +8,7 @@ module Spec
8
8
  def self.create_context_runner(args, standalone, err, out=STDOUT)
9
9
  options = parse(args, standalone, err, out)
10
10
 
11
- formatter = options.formatter_type.new(options.out, options.dry_run)
11
+ formatter = options.formatter_type.new(options.out, options.dry_run, options.colour)
12
12
  reporter = Reporter.new(formatter, options.backtrace_tweaker)
13
13
  ContextRunner.new(reporter, standalone, options.dry_run, options.spec_name)
14
14
  end
@@ -25,7 +25,11 @@ module Spec
25
25
  opts.separator ""
26
26
 
27
27
  opts.on("--diff", "Show unified diff of Strings that are expected to be equal when they are not") do
28
- require 'spec/api/helper/diff'
28
+ require 'spec/expectations/diff'
29
+ end
30
+
31
+ opts.on("-c", "--colour", "--color", "Show coloured (red/green) output") do
32
+ options.colour = true
29
33
  end
30
34
 
31
35
  opts.on("-s", "--spec SPECIFICATION_NAME", "Execute a single specification") do |spec_name|