David C. 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. The original team, without enough refactoring,
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 K.'s over-educated arguments against TDD:
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. 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.
Note that RSpec requires the constructor to BeHtmlWith to be a little
…
fruity, so I wrapped it and its Builder stuff up into
assemble_BeHtmlWith…
–
Phlip