[rspec-users] [rspec-devel] Specification Reuse to avoid Combinatorial Explosions

Brian Takita brian.takita at gmail.com
Tue Nov 21 20:35:06 EST 2006


On 11/21/06, David Chelimsky <dchelimsky at gmail.com> wrote:
>
> On 11/21/06, Brian Takita <brian.takita at gmail.com> wrote:
> > Hello,
> >
> > While reading Dan North's BDD tutorial, I tried to implement his ATM
> example
> > as spec stubs.
> > When I first implemented it creating a context for each of his
> scenarios, I
> > noticed that there is duplication and a combinatorial explosion of the
> > specs.
> >
> > I attached the full files to this email. For brevity, I will use
> scenario 1
> > in the body of this email. For the purpose of this exercise, one should
> > imagine that each specification is at least 5 lines.
> >
> > Here is how I implemented scenario 1:
> >
> > context %{A Withdrawal where an Account is in credit
> >         AND the card is valid
> >         AND the dispenser contains cash
> >          AND the customer requests cash} do
> >
> >   specify "should debit the account" do
> >   end
> >   specify "should dispense cash" do
> >   end
> >   specify "should return the card" do
> >   end
> > end
> >
> > That got me experimenting on reusing specifications by using a module to
>
> > create the specs. This is in
> > customer_withdraws_cash_spec.rb:
> >
> > module CustomerWithdrawsCashSpec
> >    def should_debit_the_account
> >     specify "should debit the account" do
> >     end
> >   end
> >   ...
> > end
> > ...
> > context %{A Withdrawal where an Account is in credit
> >          AND the card is valid
> >         AND the dispenser contains cash
> >         AND the customer requests cash} do
> >
> >    extend CustomerWithdrawsCashSpec
> >   should_debit_the_account
> >   should_dispense_cash
> >    should_return_the_card
> > end
> >
> > This is currently possible with rspec 0.7.2 .
> > Whats interesting about putting all of the specifications into a module
> is
> > that you can reduce the context to a few lines.
> > This makes each context (scenario readable) making it more feasible to
> have
> > multiple contexts in a single file such as this group of scenarios
> > represented as contexts.
> >
> > I also played with a more DSL-like syntax for this, which does not work
> for
> > rspec 0.7.2. This is in
> > customer_withdraws_cash_alternative_spec.rb::
> >
> > CustomerWithdrawsCashSpec = context_module do
> >    specify "should debit the account" do
> >   end
> >    ...
> > end
> >
> >  context %{A Withdrawal where an Account is in credit
> >         AND the card is valid
> >         AND the dispenser contains cash
> >          AND the customer requests cash} do
> >
> >   extend CustomerWithdrawsCashSpec
> >   specify "should debit the account"
> >    specify "should dispense cash"
> >   specify "should return the card"
> >  end
> >
> > As well as:
> >
> > context %{A Withdrawal where an Account is in credit
> >          AND the card is valid
> >          AND the dispenser contains cash
> >          AND the customer requests cash} do
> >
> >    specify "should debit the account", CustomerWithdrawsCashSpec
> >    specify "should dispense cash", CustomerWithdrawsCashSpec
> >    specify "should return the card", CustomerWithdrawsCashSpec
> >  end
> >
> > Or just keep the underscore syntax:
> >
> > context %{A Withdrawal where an Account is in credit
> >          AND the card is valid
> >         AND the dispenser contains cash
> >         AND the customer requests cash} do
> >
> >    extend CustomerWithdrawsCashSpec
> >   should_debit_the_account
> >   should_dispense_cash
> >    should_return_the_card
> > end
>
> Interesting ideas. If we pursue this, I'd like to find an approach
> that aligns w/ RSpecs current lack of constructs like classes and
> modules. So rather than
>
> CustomerWithdrawsCashSpec = context_module do ...
>
> context "..." do
>   extend CustomerWithdrawsCashSpec
>
> I'd like something like
>
> context_module :customer_withdraws_cash do
>
> context "...", :customer_withdraws_cash do
>
> That's not exactly right, but you get the idea.
>
> Also - I think it would be really helpful for this discussion if you
> *would* post the full specs so we can see what this stuff really looks
> like in context.


I sent the files as attachments. I guess they got rejected.

Here is the file that works now:

require "rubygems"
require "spec"

ARGV << "--format"
ARGV << "specdoc"

=begin
Title: Customer withdraws cash
As a customer,
I want to withdraw cash from an ATM,
so that I don't have to wait in line at the bank.
=end

module CustomerWithdrawsCashSpec
  def should_debit_the_account
    specify "should debit the account" do
    end
  end

  def should_display_a_rejection_message
    specify "should display a rejection message" do
    end
  end

  def should_display_an_error_message
    specify "should display an error message" do
    end
  end

  def should_dispense_cash
    specify "should dispense cash" do
    end
  end

  def should_not_dispense_cash
    specify "should not dispense cash" do
    end
  end

  def should_return_the_card
    specify "should return the card" do
    end
  end

  def should_not_return_the_card
    specify "should not return the card" do
    end
  end
end

context %{A Withdrawal where an Account is in credit
        AND the card is valid
        AND the dispenser contains cash
        AND the customer requests cash} do

  extend CustomerWithdrawsCashSpec
  should_debit_the_account
  should_dispense_cash
  should_return_the_card
end

context %{A Withdrawal where an Account is in credit
        AND the card is NOT valid
        AND NOT stolen
        AND the dispenser contains cash
        AND the customer requests cash} do

  extend CustomerWithdrawsCashSpec
  should_display_a_rejection_message
  should_not_dispense_cash
  should_return_the_card
end

context %{A Withdrawal where an Account is in credit
        AND the card is NOT valid
        AND stolen
        AND the dispenser contains cash
        AND the customer requests cash} do

  extend CustomerWithdrawsCashSpec
  should_display_a_rejection_message
  should_not_dispense_cash
  should_not_return_the_card
end

context %{A Withdrawal where an Account is in credit
        AND the card is valid
        AND the dispenser DOES NOT contain cash
        AND the customer requests cash} do

  extend CustomerWithdrawsCashSpec
  should_display_an_error_message
  should_not_dispense_cash
  should_return_the_card
end

context %{A Withdrawal where an Account is overdrawn
        AND the card is valid
        AND the customer requests cash} do

  extend CustomerWithdrawsCashSpec
  should_display_a_rejection_message
  should_not_dispense_cash
  should_return_the_card
end

And here is the alternative source:

require "rubygems"
require "spec"

ARGV << "--format"
ARGV << "specdoc"

=begin
Title: Customer withdraws cash
As a customer,
I want to withdraw cash from an ATM,
so that I don't have to wait in line at the bank.
=end

CustomerWithdrawsCashSpec = context_module do
  specify "should debit the account" do
  end

  specify "should display a rejection message" do
  end

  specify "should display an error message" do
  end

  specify "should dispense cash" do
  end

  specify "should not dispense cash" do
  end

  specify "should return the card" do
  end

  specify "should not return the card" do
  end
end

context %{A Withdrawal where an Account is in credit
        AND the card is valid
        AND the dispenser contains cash
        AND the customer requests cash} do

  extend CustomerWithdrawsCashSpec
  specify "should debit the account"
  specify "should dispense cash"
  specify "should return the card"
end

context %{A Withdrawal where an Account is in credit
        AND the card is NOT valid
        AND NOT stolen
        AND the dispenser contains cash
        AND the customer requests cash} do

  specify "should display a rejection message", CustomerWithdrawsCashSpec
  specify "should not dispense cash", CustomerWithdrawsCashSpec
  specify "should return the card", CustomerWithdrawsCashSpec
end

context %{A Withdrawal where an Account is in credit
        AND the card is NOT valid
        AND stolen
        AND the dispenser contains cash
        AND the customer requests cash} do

  extend customer_withdraws_cash
  specify "should display a rejection message"
  specify "should not dispense cash"
  specify "should not return the card"
end

context %{A Withdrawal where an Account is in credit
        AND the card is valid
        AND the dispenser DOES NOT contain cash
        AND the customer requests cash} do

  specify "should display an error message", customer_withdraws_cash
  specify "should not dispense cash", customer_withdraws_cash
  specify "should return the card", customer_withdraws_cash
end

context %{A Withdrawal where an Account is overdrawn
        AND the card is valid
        AND the customer requests cash} do

  extend CustomerWithdrawsCashSpec
  should_display_a_rejection_message
  should_not_dispense_cash
  should_return_the_card
end



Thanks Brian.
>
> David
> _______________________________________________
> rspec-devel mailing list
> rspec-devel at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-devel
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://rubyforge.org/pipermail/rspec-users/attachments/20061121/199e7d57/attachment.html 


More information about the rspec-users mailing list