[rspec-devel] [ rspec-Feature Requests-8126 ] Nested specify blocks

noreply at rubyforge.org noreply at rubyforge.org
Fri Feb 16 10:34:46 EST 2007


Feature Requests item #8126, was opened at 2007-01-25 02:25
You can respond by visiting: 
http://rubyforge.org/tracker/?func=detail&atid=3152&aid=8126&group_id=797

Category: expectation module
Group: None
Status: Open
Priority: 3
Submitted By: Wincent Colaiuta (wincent)
Assigned to: Nobody (None)
Summary: Nested specify blocks

Initial Comment:
I know that the idea of nested contexts has come up on the devel list before (Google for "pipermail rspec nested contexts") and most were not in favor of the idea.

For me, more useful than nested contexts would be nested specifications ("specify" blocks). Currently I have specs which look like this:

context 'outer' do
  specify 'inner' do
    this.should == that
  end
end

If a spec is not satisfied then rspec prints a message of the form "outer inner". I sometimes find myself wishing that I could nest the specify blocks to fine tune the failure message:

context 'outer' do
  specify 'inner' do
    specify 'innermost' do
      this.should == that
    end
  end
end

In this case rspec would print "outer inner innermost" on failure. Let me flesh it out a bit with more of a concrete example; basically I often find myself wanting to write this:

context 'applying a regexp to a string' do
  specify 'should be able to match "zero or more" times' do
    specify 'with input that matches zero times' do
      # test zero-match case
    end
    specify 'with input that matches one time' do
      # test one-match case
    end
    specify 'with input that matches two times' do
      # test two-match case
    end      
  end
end

Because I can't be bothered writing this:

context 'applying a regexp to a string' do
  specify 'should be able to match "zero or more" times (with input that matches zero times)' do
    # test zero-match case
  end
  specify 'should be able to match "zero or more" times (with input that matches one time)' do
    # test one-match case
  end
  specify 'should be able to match "zero or more" times  (with input that matches two times)' do
    # test two-match case
  end      
end

But I end up writing this:

context 'applying a regexp to a string' do
  specify 'should be able to match "zero or more" times' do
    # test zero-match case
    # test one-match case
    # test two-match case
  end      
end

This isn't ideal because if the first assertion fails then the others aren't even tested. I'd rather keep the number of assertions in each specify block a bit lower, and the ability to nest specify blocks would help me to do that. What do people think?

----------------------------------------------------------------------

Comment By: Daniel Schierbeck (dasch)
Date: 2007-02-16 16:34

Message:
Why not also nest the output?

<pre><code>
  Applying a regexp to a string
  - should be able to match "zero or more" times
     * with input that matches zero times
     * with input that matches one time
     * with input that matches two times
</code></pre>

----------------------------------------------------------------------

Comment By: David Chelimsky (dchelimsky)
Date: 2007-02-05 12:46

Message:
Lachie: yes, you could view it that way, and given/when/then should be supported keywords (when and then would have to be munged in some way as they are already Ruby keywords).

Wincent: we've gone a long way to keep things simple and from what I've seen, some people will go out of their way to complicate things no matter how simple you make them. Including us!!!! (e.g. controller.should_render)

If/when we do this, it will likely be introduced gradually. It's not the first priority at the moment ;)

Also - looking back at your initial submission - you could do this in the mean time:

context 'applying a regexp to a string should be able to match "zero or more" times' do
  specify 'with input that matches zero times' do
  end
  specify 'with input that matches one time' do
  end
  specify 'with input that matches two times' do
  end      
end

I realize that the context name is more than context - it's a declaration of an aspect of behaviour. That's probably not such a bad thing. And it would result in the same readable output on failures. FFT.

----------------------------------------------------------------------

Comment By: Wincent Colaiuta (wincent)
Date: 2007-02-05 11:12

Message:
> Command line options could load a file which defines
> custom aliases that you want to use that don't ship
> with RSpec, so you could things like 
> "project/iteration/story/scenario" if that made you
> happy.

Above all, I think *some kind* of nesting support would be a 
wonderful improvement in terms of readability/expressiveness 
and also in terms of saved typing; these are the reasons why 
I filed the feature request.

I am also sure that the ability to basically twist RSpec 
into any shape you want and use any terminology you want 
would be popular.

BUT I also think that it might be a bit dangerous to give 
people too much free reign. One of the things about BDD in 
general and specifically RSpec is that the language that it 
imposes ("context"/"specify") helps developers to do the 
right thing and follow best testing practice. If people 
could use any terminology at all then some of that benefit 
might be lost. Also, in the same way that overuse of C 
preprocessor macros can make it harder to read other 
people's source code, if the terminology for specifying 
behaviour is made too flexible then it might become more 
difficult to read other people's specs.

So I'm not saying that you shouldn't do it, but that if you 
do decide to go down this path I think your idea of doing so 
in an incremental fashion is a good one; it will give time 
to really think about it and be sure of why/how/if it should 
be done.

I like the idea of using "story" for acceptance level 
testing. I am not sure whether "behaviour" is the best term 
for cases where "the context is not
so much a system state, but rather an aspect of behaviour"; 
I think the word that you used right there, "aspect", is 
probably a better choice. That's just my initial gut 
feeling; I'd need to think about it more to be sure.

----------------------------------------------------------------------

Comment By: Lachie Cox (lachie)
Date: 2007-02-04 23:29

Message:
I this given-when-then by different names?

----------------------------------------------------------------------

Comment By: David Chelimsky (dchelimsky)
Date: 2007-02-04 23:21

Message:
Thinking about this some more - I think that there are many structures that could prove useful. We've named "context/specify/example", but there is also "story/scenario" for acceptance level spec'ing, "behaviour/specify/example" when the context is not so much a system state, but rather an aspect of behaviour like "error messages".

This is leading me to this thought: what if the structure was simply a tree?

We could have a method #rspec_node(*args, &block) that returns an RSpecNode. This would be implemented in Kernel, aliased with names like #context, #behaviour and #story. It would also be implemented in RSpecNode, aliased with names like #specify, #example and #scenario.

Nested nodes would have access to state in any of its ancestor nodes, supporting the long sought-after "nested context", but siblings would not have access to each other's state.

Command line options could load a file which defines custom aliases that you want to use that don't ship with RSpec, so you could things like "project/iteration/story/scenario" if that made you happy.

I've taken a look at this and it would require a lot of work, but there's no reason it shouldn't be 100% backwards compatible (except for monkey patching), and it's something we can probably introduce gradually, starting w/ nested specify blocks, and working up to unifying Context and Specification into a single RSpecNode.

WDYT of that?

----------------------------------------------------------------------

Comment By: Wincent Colaiuta (wincent)
Date: 2007-01-27 17:42

Message:
Yes, I think that "context/specify/example" would be a great 
way to split things up, and the language you've proposed 
maps well onto the behaviour-driven way of thinking about 
software development ("in this context ... this should 
happen ... example ...").

About the only thing I would change in your example is that 
I would start each example with the word "for" so that when 
the three strings are chained together they would read like, 
"applying a regexp to a string should be able to match 'zero 
or more' times for the zero-match case" etc:

context 'applying a regexp to a string' do
  specify 'should be able to match "zero or more" times' do
    example 'for the zero-match case' do ... end
    example 'for the one-match case' do ... end
    example 'for the two-match case' do ... end
  end      
end

This has the added benefit that the line introducing each 
example in the specification reads as "example for zero-
match case" etc, which is highly readable and descriptive.

----------------------------------------------------------------------

Comment By: David Chelimsky (dchelimsky)
Date: 2007-01-27 16:53

Message:
I love this idea, but I think nesting specify would be confusing to look at. How about this:

context
  specify
    example

context 'applying a regexp to a string' do
  specify 'should be able to match "zero or more" times' do
    example "zero-match case" do
    example "one-match case" do
    example "two-match case" do
  end      
end

----------------------------------------------------------------------

You can respond by visiting: 
http://rubyforge.org/tracker/?func=detail&atid=3152&aid=8126&group_id=797


More information about the rspec-devel mailing list