[rspec-users] Rspecing an enumerator from outside-in woes
Matt Wynne
matt at mattwynne.net
Sun Mar 1 13:53:38 EST 2009
On 1 Mar 2009, at 17:30, Shot (Piotr Szotkowski) wrote:
> Thanks a lot, Matt, for your reply! It’s seriously most enlightening.
>
> Matt Wynne:
>
>> On 28 Feb 2009, at 21:29, Shot (Piotr Szotkowski) wrote:
>
>>> 1. A philosophical/kosherness question: In the finished system
>>> Decomposer#each will yield Decomposition objects, but as I’m specing
>>> from outside-in, the Decomposition class is not yet created. In the
>>> attached example I’m using an Array as a poor man’s Decomposition
>>> replacement. Is this a sane approach, or should I rather create
>>> a skeletal Decomposition#initialize instead?
>
>> I think you should try to write the specs so they won't have to
>> change
>> when you build the real implementation. That doesn't mean creating an
>> actual Decomposition class just yet, but it does mean that you should
>> return something that looks enough like one for the tests to still be
>> valid when you swap one in.
>
> Hmm, interesting – so an outside-in implementation should side-step
> using future classes’ constructors, and the implementation code should
> actually change when the relevant classes appear?
I wouldn't suggest you did anything that meant you had to change the
implementation later when you replace the fake with a real object.
Assuming you wanted to keep your focus on the Decomposer class, but
you knew that the concept of a Decomposition was a firm one in your
design, you could define an empty Decomposition class, then stub the
constructor to return a test double of some sort:
class Decomposition
end
...
Decomposition.stub!(:new).and_return mock('Decomposition', :foo
=> 'bar')
> I ended up creating a skeletal Decomposition class, but then had to
> add Decomposition#==(other) – which, in turn, made me add attribute
> accessors – just to be able to test against Decomposition objects in
> RSpec.
>
> Decomposition#== will be useful in the future, but currently it exists
> solely so I can use RSpec’s ….should == Decomposition.new(…), which
> seems wrong. Hmm, another thing to ponder upon – every time a new
> RSpec
> paradigm shows me something new, some other, unrelated spec begins to
> raise suspicions… :)
I'm less fussy these days about adding a bit of code to make something
testable - I think of it a bit like adding screws to let you take an
appliance like a CD player apart, rather than sealing it all up with
glue. Having said that, if you have to work hard to do this, there
might be a smell in your design. You could consider the Decomposition
with all those nasty getters all over it to be something you only use
in your tests - a TestableDecomposition. One technique for creating a
test double is to subclass the object you want to fake, and add extra
behaviour in the subclass that make the object more testable, without
adding its behaviour. Exposing some state in the form of getters, or
adding #== might be an example of this.
Ideally though, you really want to avoid testing state and instead
think about testing the interactions between objects. If the
relationship between the Decomposer and the Decomposition is for one
to create instances of the other, then I would be quite satisfied
writing mock expectations on the Decomposition's constructor, like this:
Decomposition.should_receive(:new).with [1,2]
>> I think what you're finding clumsy here is the mocking setup. You
>> don't always have to use mock objects as your 'test doubles' and
>> often
>> it's much easier (and leaves behind more flexible tests) if you use
>> stubs rather than mocks.
>
> Thanks a ton for the Array-based generators – I haven’t thought of
> that;
> they are most interesting. I can’t use your example verbatim, as in my
> real code Decomposer.new takes class names and only then instantiates
> the relevant generators¹, but it surely opened my eyes on stubbing
> objects with general-purpose classes rather than mocking them. I’ll
> see how I can use them to clean-up the specs. :)
>
> ¹ http://github.com/Chastell/art-decomp/commit/f9f8d3b2a3e431290d0656f7244b64f5376fab8f
>
>>> 3. …so I came up with the second, Decomposer.new.to_enum approach,
>>> which simply validates the enumrator’s #next objects. Unfortunately,
>>> this does not seem to trigger #should_receive(:each) on the *_gen
>>> mocks and made me #stub!(:each) on them instead. Is this because
>>> I’m using RSpec 1.1.12 with Ruby 1.9.1, or am I missing something
>>> on how message expectations work with lazy iterators (and, thus,
>>> #should_receive fail properly)?
>
>> I think you're getting too far into specifying the
>> implementation here. I like the #each approach better.
>
> I think I agree – but the real question was why don’t the (lazy)
> enumerator’s #next calls satisfy the mocks’ #should_receive()s –
> am I missing something fundamental, or is this simply an RSpec 1.1.12
> incompatibility with Ruby 1.9.1?
>
> For reference, my original attachment: http://gist.github.com/72399 –
> if you replace the #stub!()s in the second spec with
> #should_receive()s,
> the spec breaks with (allegedly) unsatisfied expectations.
Sorry, not sure about that one - I've not tried playing with these
lazy enumerators - is this a Ruby 1.9 thing
Matt Wynne
http://blog.mattwynne.net
http://www.songkick.com
More information about the rspec-users
mailing list