[rspec-users] Mocking: brittle specs and tight coupling?

David Chelimsky dchelimsky at gmail.com
Sun Apr 12 22:10:49 EDT 2009


On Sun, Apr 12, 2009 at 9:23 PM, Phlip <phlip2005 at gmail.com> wrote:
> David Chelimsky wrote:
>
>>> Another tip: To TDD a new feature, don't clone a high-level test which
>>> calls
>>> code which calls code which calls the code you need to change.
>>
>> FWIW, what you propose is the exact opposite of BDD, which suggests we
>> start at the highest levels and work our way in.
>
> My current day-job's most important project has a test suite that suffered
> from abuse of that concept.

Any concept can be abused. No reason to ignore its merits. Similarly,
ideas that work well in one situation shouldn't be assumed to work
well in all situations. It cuts both ways.

> The original team, without enough refactoring,

"without enough refactoring" suggests that the problem was the team,
not the concept. No?

> were cloning and modifying very-high-level tests, making them longer, and
> using them to TDD new features into the bottom of the models layer. Don't do
> that.
>
>> How can you know what the lowest level code is supposed to do before
>> you have higher level code invoking it? You can certainly make good
>> guesses, and they might end up being good choices in the end, but they
>> tend to bind you to a pre-determined design.
>
> That sounds like James Kanze's over-educated arguments against TDD:
>
>> Phlip wrote:
>>>
>>> James Kanze wrote:
>>> > In particular, you can't
>>> > write a single line of code (unit test or implementation) before
>>> > you know what the function should do,
>>
>>> I didn't bring up TDD, but if you are curious enough about it
>>> to keep asking these entry-level FAQs,
>>
>> I'm not asking anything.  I'm simply stating an easily
>> established fact, which wishful thinking seems to cause some
>> people to ignore.  Tests definitly have their role, but until
>> you know what the code should do, you can't begin to write them.
>> And until you've written something down in black and white, you
>> don't know it.
>
> From there, he'll wander off into "too smart to just try it" bullshit...
>
>> Your recommendation also starts with cloning a pre-existing example,
>> so I'm assuming this is a very specific sort of situation, where you
>> have a certain amount of code in place. What about when you are
>> starting a project for the first time?
>
> Programming bottom-up gives you decoupled lower layers. Top-down gives you a
> way to tunnel from a new View feature into the code that supports it.

I don't think these are mutually exclusive. You can develop decoupled
layers driving from the outside-in. And when you do, the inner layers,
which serve the needs of the outer layers, tend to be more focused on
that responsibility than if you start with the inner layers because
you're not guessing what you'll need, you're satisfying an existing
need.

> The
> goal is you _could_ start a new feature either way, and you get the benefits
> of both techniques.
>
> I thought of describing that specific tip while adding "any!" to
> assert_xhtml. It would have been too easy to start with the high-level
> tests:
>
>  def test_anybang_is_magic
>    assert_xhtml SAMPLE_LIST do
>      ul.kalika do
>        any! 'Billings report'
>      end
>    end
>
>    assert_xhtml_flunk SAMPLE_LIST do
>      without! do
>        any! 'Billings report'
>      end
>    end
>  end
>
> Some of my features indeed started there, and some of them indeed do not yet
> have low-level tests.
>
> But the entire call stack below that also at least has tests on each layer -
> except the actual code which converts a tag like select! into the fragment
> of XPath which matches //select[]. Oh, and that code around it had grown a
> little long. So in this case, I started there, and refactored out the single
> line that needed the change:
>
>  def translate_tag(element)
>    element.name.sub(/\!$/, '')
>  end
>
> Then I can TDD translate_tag directly:
>
>  def test_any_element
>    bhw = assemble_BeHtmlWith{ any :attribute => 'whatever' }
>    element = bhw.builder.doc.root
>    assert{ bhw.translate_tag(element) == 'any' }
>    bhw = assemble_BeHtmlWith{ any! :attribute => 'whatever' }
>    element = bhw.builder.doc.root
>    assert{ bhw.translate_tag(element) == '*' }
>  end
>
> ...
>
>  def translate_tag(element)
>    if element.name == 'any!'
>      '*'
>    else
>      element.name.sub(/\!$/, '')
>    end
>  end
>
> Only then I wrote the high-level tests, and they passed.

>From what I'm reading, this seems like a very specific situation
you're describing in which you did, in fact, start from the
outside-in. The lower level test you added was the result of
refactoring, which seems perfectly reasonable to me. But I'm not
seeing how this applies as a general all-purpose guideline. What am I
missing?

>
> Note that RSpec requires the constructor to BeHtmlWith to be a little ...
> fruity

Huh? RSpec does not construct matcher instances for you, so how is it
enforcing any constructor restrictions, fruity or otherwise?

> so I wrapped it and its Builder stuff up into assemble_BeHtmlWith...
>
> --
>  Phlip
>
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users
>


More information about the rspec-users mailing list