Skip to content
Browse files

implement And clause

* add Also clause
* move also_spec.rb to and_spec.rb
* rename Also => And
* rename _rg_ands => _rg_and_blocks
* document And clause
  • Loading branch information...
1 parent b9ea220 commit 3e7d61b12ca7c02a7eea93608eb691da1f366c49 @jimweirich committed Sep 12, 2012
Showing with 147 additions and 7 deletions.
  1. +5 −1 Gemfile
  2. +2 −0 Gemfile.lock
  3. +65 −6 README.md
  4. +48 −0 examples/integration/and_spec.rb
  5. +4 −0 examples/spec_helper.rb
  6. +23 −0 lib/rspec/given/extensions.rb
View
6 Gemfile
@@ -1,3 +1,7 @@
source 'https://rubygems.org'
gem 'rspec', '>= 2.0'
-gem 'rake', '>= 0.9.2.2'
+gem 'rake', '>= 0.9.2.2'
+
+group :test do
+ gem 'flexmock'
+end
View
2 Gemfile.lock
@@ -2,6 +2,7 @@ GEM
remote: https://rubygems.org/
specs:
diff-lcs (1.1.3)
+ flexmock (1.0.2)
rake (0.9.2.2)
rspec (2.11.0)
rspec-core (~> 2.11.0)
@@ -16,5 +17,6 @@ PLATFORMS
ruby
DEPENDENCIES
+ flexmock
rake (>= 0.9.2.2)
rspec (>= 2.0)
View
71 README.md
@@ -46,7 +46,7 @@ describe Stack do
When { stack.push(:an_item) }
Then { stack.depth.should == 1 }
- Then { stack.top.should == :an_item }
+ And { stack.top.should == :an_item }
end
context "when popping" do
@@ -81,8 +81,8 @@ describe Stack do
When(:pop_result) { stack.pop }
Then { pop_result.should == :top_item }
- Then { stack.top.should == :second_item }
- Then { stack.depth.should == original_depth - 1 }
+ And { stack.top.should == :second_item }
+ And { stack.depth.should == original_depth - 1 }
end
end
end
@@ -215,13 +215,25 @@ those given to the standard RSpec matcher 'raise_error'.
### Then
-The _Then_ sections are the postconditions of the specification. These
+The _Then_ clauses are the postconditions of the specification. These
then conditions must be true after the code under test (the _When_
-block) is run.
+clause) is run.
-The code in the _Then_ block should be a single _should_
+The code in the block of a _Then_ clause should be a single _should_
assertion. Code in _Then_ blocks should not have any side effects.
+In RSpec terms, a _Then_ clause forms a RSpec Example that runs in the
+context of an Example Group (defined by a describe or context clause).
+
+Each Example Group must have at least one _Then_ clause, otherwise
+there will be no examples to be run for that group. If all the
+assertions in an example group are done via Invariants, then the group
+should use an empty _Then_ clause, like this:
+
+<pre>
+ Then { }
+</pre>
+
#### Then examples:
<pre>
@@ -231,6 +243,47 @@ assertion. Code in _Then_ blocks should not have any side effects.
After the related _When_ block is run, the stack should be empty. If
it is not empty, the test will fail.
+### And
+
+The _And_ clause is similar to _Then_, but do not form their own RSpec
+examples. This means that _And_ clauses reuse the setup from the
+_Then_ clause. Using a single _Then_ an multiple _And_ clauses in an
+example group means the setup for that group is run only once (for the
+_Then_ clause). This can be a significant speed savings where the
+setup for an example group is expensive.
+
+Some things to keep in mind about _And_ clauses:
+
+1. There must be at least one _Then_ in the example group and it must
+ be declared before the _And_ clauses. Forgetting the _Then_ clause
+ is an error.
+
+1. The code in the _And_ clause is run immediately after the first
+ _Then_ of an example group.
+
+1. And assertion failures in a _Then_ clause or a _And_ clause will
+ cause all the subsequent _And_ clauses to be skipped.
+
+1. Since _And_ clauses do not form their own RSpec examples, they are
+ not represented in the formatted output of RSpec. That means _And_
+ clauses do not produce dots in the Progress format, nor do they
+ appear in the documentation, html or textmate formats (options
+ -fhtml, -fdoc, or -ftextmate).
+
+The choice to use an _And_ clause is primarily a speed consideration.
+If an example group has expensive setup and there are a lot of _Then_
+clauses, then choosing to make some of the _Then_ clauses into _And_
+clause will speed up the spec. Otherwise it is probably better to
+stick with _Then_ clauses.
+
+#### Then examples:
+
+<pre>
+ Then { pop_result.should == :top_item }
+ And { stack.top.should == :second_item }
+ And { stack.depth.should == original_depth - 1 }
+</pre>
+
### Invariant
The _Invariant_ block is a new idea that doesn't have an analog in
@@ -249,6 +302,12 @@ that context.
Invariants that reference a _Given_ precondition accessor must only be
used in contexts that define that accessor.
+Notes:
+
+1. Since Invariants do not form their own RSpec example, they are not
+ represented in the RSpec formatted output (e.g. the '--format html'
+ option).
+
# Future Directions
I really like the way the Given framework is working out. I feel my
View
48 examples/integration/and_spec.rb
@@ -0,0 +1,48 @@
+require 'rspec/given'
+require 'spec_helper'
+
+describe "And" do
+ Given(:info) { [] }
+ Given(:mock) { flexmock("mock") }
+
+ describe "And is called after Then" do
+ Given { mock.should_receive(:and_ran).once }
+ Then { info << "T" }
+ And {
+ info.should == ["T"]
+ mock.and_ran
+ }
+ end
+
+ describe "And is called only once with multiple Thens" do
+ Then { info << "T" }
+ Then { info << "T2" }
+ And { info.should == ["T"] }
+ end
+
+ describe "Inherited Ands are not run" do
+ Then { info << "T-OUTER" }
+ And { info << "A-OUTER" }
+ And { info.should == ["T-OUTER", "A-OUTER"] }
+
+ context "inner" do
+ Then { info << "T-INNER" }
+ And { info << "A-INNER" }
+ And { info.should == ["T-INNER", "A-INNER"] }
+ end
+ end
+
+ describe "Ands require a Then" do
+ begin
+ And { }
+ rescue StandardError => ex
+ @message = ex.message
+ end
+
+ it "should define a message" do
+ message = self.class.instance_eval { @message }
+ message.should =~ /and.*without.*then/i
+ end
+ end
+
+end
View
4 examples/spec_helper.rb
@@ -1,2 +1,6 @@
require 'rspec/given'
$LOAD_PATH << './examples/stack'
+
+RSpec.configure do |c|
+ c.mock_with :flexmock
+end
View
23 lib/rspec/given/extensions.rb
@@ -31,11 +31,20 @@ def _rg_check_invariants # :nodoc:
end
end
+ def _rg_check_ands # :nodoc:
+ return if self.class._rg_context_info[:and_ran]
+ self.class._rg_and_blocks.each do |block|
+ instance_eval(&block)
+ end
+ self.class._rg_context_info[:and_ran] = true
+ end
+
# Implement the run-time semantics of the Then clause.
def _rg_then(&block) # :nodoc:
_rg_establish_givens
_rg_check_invariants
instance_eval(&block)
+ _rg_check_ands
end
end
@@ -53,6 +62,14 @@ def _rg_invariants # :nodoc:
@_rg_invariants ||= []
end
+ def _rg_and_blocks
+ @_rg_and_blocks ||= []
+ end
+
+ def _rg_context_info
+ @_rg_contet_info ||= {}
+ end
+
# Trigger the evaluation of a Given! block by referencing its
# name.
def _rg_trigger_given(name) # :nodoc:
@@ -145,13 +162,19 @@ def Then(&block)
file = eval "__FILE__", b
line = eval "__LINE__", b
eval %{specify do _rg_then(&block) end}, binding, file, line
+ _rg_context_info[:then_defined] = true
end
# Establish an invariant that must be true for all Then blocks
# in the current (and nested) scopes.
def Invariant(&block)
_rg_invariants << block
end
+
+ def And(&block)
+ fail "And defined without a Then" unless _rg_context_info[:then_defined]
+ _rg_and_blocks << block
+ end
end
end
end

0 comments on commit 3e7d61b

Please sign in to comment.
Something went wrong with that request. Please try again.