On May 4, 2011, at 5:25 PM, nbenes wrote:
> I consider myself an intermediate-to-advanced rubyist and rails
> programmer. I''ve embraced TDD but sometimes wonder if
I''m just doing
> it wrong. I feel like it can take me longer to get the specs done
> then should be necessary.
>
> I''ll give a very specific example from a project I was working on
last
> night. I was working on a feature in a personal budget-management
> application to be able to deposit and withdraw funds from/to an
> ''account''.
>
> I wrote a request/integration spec using capybara to go through the
> interaction from a browser''s point of view - I went through every
> possibility:
>
> If a user deposits funds, the new amount should be displayed for the
> account
> If a user withdraws funds:
> If there are enough funds remaining show the new amount
> If there are not enough funds only allow the amount field to become
> negative if the negative_overflow_id is the account itself (show the
> new amount)
> If there are not enough funds and the negative_overflow_id is
> another account, remove the remaining amount from that account and
> display the new amounts on both this account and the other account.
> If there are not enough funds and no negative_overflow_id is set,
> return an error on the field to the user indicating the problem.
>
> This part I''m ok with, I think there should always be a
walkthrough of
> the full process that takes the entire stack into account. Since
I''m
> somewhat thorough in my request specs, I tend to skip view tests and
> most of the controller tests (I''ll throw in some controller tests
for
> certain cases) because all of them together just take an inordinate
> amount of time to finish.
>
>
> I run the tests, I get red.
If this is the first time you ran the specs and you have 6 failing request
specs, then this is not TDD. The idea of TDD is that you write one example, and
maybe not the whole example - just enough of an example to get red - and then
write just enough implementation code to change the message or make it pass.
http://teachmetocode.com/articles/8-lessons-from-corey-haines-performance-kata/
http://aac2009.confreaks.com/07-feb-2009-13-30-tatft-the-laymans-guide-bryan-liles.html
Once passing, you look for refactoring opportunities in both the code and spec.
When you first start this, there isn''t much to refactor yet, so you
probably just skip to the next step: either add more to that example (if it is
not complete yet) or move on to a new example.
After you have a couple of passing examples, the refactoring opportunities start
to appear.
At each stage, try to think of the system _right now_ as a complete system based
on the examples that are already passing. If the first example you write is
this:
describe Account do
context "when first created" do
it "has a zero balance" do
#...
end
end
end
... then, at this point, you have an application that lets you create an account
object with a zero balance. Does the code you have right now serve its purpose
well? Are the names all the right names, given that this is the entire
application? Is the code as well factored as it needs to be to satisfy this
single requirement?
There is a saying that as soon as you get the first example passing, the rest of
your job is maintenance. So now you add a second example using the same process.
Add just enough test to get red, then just enough implementation to get green,
then look for refactoring opportunities. Rinse, repeat.
It take a fair amount of discipline to commit to this cycle, but once you get
the hang of it it moves really, really quickly.
> I throw together the view and controller
> enough so that the tests give me this error:
>
> undefined method update_amount on Account
>
> My controller is thin, and the view is simple. One field (amount)
> with two submit buttons ("Withdraw" and "Deposit").
The controller
> does this:
>
> if @account.update_amount(params[:account][:amount], params[:commit])
>
> All is well in the world. Now I drop down to model specs. I write
> all of the expected cases for the model first before even defining the
> method. (Is this how you''re supposed to do it?
>
> My Account model spec ends up having 27 assertions (27 separate it
> "should" blocks, mind you - I''ve joined the
''don''t put multiple
> assertions in one it block'' camp) for this one method.
>
> The method appears that it is going to attempt to do too much, and
I''m
> very aware of this as I''m writing the specs. But I want to have
the
> ''end result'' mapped out before I write up the method.
>
>
> My method, when all is said and done, ends up looking like this:
> https://gist.github.com/956156
>
> To implement the edge cases defined above I setup some before
> validation and before update filters. My spec for the method passes,
> so I know it''s doing what I want (in some fashion), but as I was
> writing the code to make it work I failed to write any specs for the
> callbacks that the model makes to implement the functionality.
>
> I''m not sure what path I *should* have taken instead. Some camps
seem
> to think that you should stub off and use .should_receive calls for
> specific behavior in a tested method to keep it isolated... but then
> how do you really know that it''s adhering to the other
methods''
> parameters and such? Also if you do that, aren''t you mocking up
> implementation in your spec which your test shouldn''t care about
at
> all (only the end result).
>
> Should I keep this test with it''s 27 assertions, and then write
> another 20 or so for all of the callback methods that help to
> implement it''s behavior? It takes so long to get the request
specs
> and method specs written and then I end up having to re-write many of
> them because of how I implemented the method.
>
> Does anyone have thoughts on how this is supposed to be done in a TDD
> fashion?
It''s difficult to know if the 27 examples are the right 27 examples
without seeing them. Feel free to post the spec if you want some more feedback,
though even seeing them isn''t necessarily going to help anyone to help
you understand the process.
I''d recommend you give two resources a read: one, of course, is The
RSpec Book. The first part of the book is a step by step tutorial that walks you
through the process of driving out some code with a combination of
application-level and object-level examples. It''s not a Rails app, but
it''s the same process as going from request specs (application level)
to model specs (object level).
For a sense of where this all started, I''d recommend Kent
Beck''s Test-Driven Development by Example. Kent is known for inventing
TDD, and the examples in his book are very simple and clear.
HTH,
David