[LRUG] Writing readable feature tests with RSpec

Sam Livingston-Gray geeksam at gmail.com
Wed Jul 30 09:32:36 PDT 2014


On Wed, Jul 30, 2014 at 7:51 AM, Paul Battley <pbattley at gmail.com> wrote:

> State within one feature feels a lot easier to deal with than hidden state
> in @variables across different definition files.


You don't need to use @variables.  Okay, technically, you need to use
*one*, but it can be something like @shared_state = {}, and then everything
else can be more explicit about how it interacts with that shared state.
 Making it a Hash allows you to use the marvelous Hash#fetch for exploding
as soon as things aren't as you expect (e.g., step A sets @color = 'fff'
and step E asks for @colour), rather than returning a nil that will
*eventually* explode.  ("Nobody expects the Spanish Inqui...NoMethodError!
 Our primary weapon is whiny nil!")

In a project I started at a previous job (that was then extensively
refactored and documented by a coworker), we referred to a user's "mental
model" and used it to update state between steps:
https://github.com/jwilger/kookaburra/blob/master/lib/kookaburra/mental_model.rb


> I found myself ordering steps so that I could reuse them without
> aliasing them to deal with given/and then/and when/and, which feels
> like a bad motivation. That led me to think that perhaps I could
> create no-op given/when/then/and methods (i.e. given_i_do_something ->
> given i_do_something) - except, of course, that all but one of those
> is a reserved word in Ruby. given_/when_/then_/and_, perhaps? Or
> precondition/action/outcome?


One possibility:  define a top-level method.  Call it whatever you like;
for purposes of argument, I'll just use 'foo'.  Make that method return an
object with:

def given ; self ; end
def when ; self ; end
def then ; self ; end

Then define all of your test helpers on that object.  All of these will now
invoke the same method without your having to think about what prefix goes
on the method name:

foo.given.i_do_something
foo.when.i_do_something
foo.then.i_do_something

If you later find yourself putting other responsibilities on the object
returned by #foo, you might want to have its given/when/then methods return
some other object instead of self.  You might also find yourself wanting to
separate out responsibilities by topic area, so that you wind up writing
step code like:

step_driver.given.i_am_logged_in_as 'sales_rep'
step_driver.when.i_have_net_revenue '$20,000'
step_driver.when.commissions_are_calculated
step_driver.then.i_receive_payout_of '$5'

To swipe a line from a recent Katrina Owen article about refactoring:
 guess how I know this.  ;>

That feels like a step along the
> dangerous path to precedence hacks and pseudo-English Ruby - I already
> feel that RSpec is a bit too far that way, and every time I see
> something like "expect(foo).to be true" I'm a little surprised that it
> works.
>

Really?  That's far better than the gyrations RSpec originally went through
to make the #should method available on literally everything in the system
(and even then, it still didn't work everywhere).  While in a
windmill-tilting mood a few years back, I once wrote a gem to quarantine
RSpec's Kernel#should methods so that my team didn't accidentally use them
in a context where they, well, should_not_have.
https://github.com/geeksam/cordon

Upon learning that RSpec had introduced the #expect syntax and was
deprecating Kernel#should, I (a) was extremely amused, (b) thought "damn,
that's a much better name than #assert_that" (which I had used in the
aforementioned gem), and (c) resolved to give RSpec another try.

I guess what I'm saying is that I can see how that construct seems a bit
silly when looked at with fresh eyes, but for RSpec, it's such a huge step
back toward sanity that it didn't occur to me to look at it that way!  :D
 (See also:  "the Overton window".)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.lrug.org/pipermail/chat-lrug.org/attachments/20140730/538189b4/attachment-0003.html>


More information about the Chat mailing list