>> [Big Snip]
>
> There are a few bad assumptions in your colleague's response, so to
> set the record straight:
>
> * test coverage and tests which use the interaction-based test
> approach are not mutually exclusive
> * you can have crappy tests which take the state-based approach and
> crappy tests which use a interaction-based approach
> * interaction-based testing is not merely limited to contrived
> examples on people's blogs, it is a real practice which adds value on
> lots of "real-world" projects
> * using factories to generate required objects in tests has several
> pros over the use of fixtures, and very very very few cons
>
> State-based testing and interaction-based testing both have their
> place. There are number of reasons why they are both useful, but I'm
> going to pick two: object decomposition (and coordinators) and
> integration testing. Others have mentioned the value of writing tests
> with the interface you want so I'm going to leave that out.
>
> As an application grows in features and complexity (business logic of
> course) good developers will decompose the problem into a number of
> simple objects. Some of these objects are responsible for doing the
> work and others are responsible for coordinating other objects to do
> the work. Objects which are responsible for coordinating are great
> candidates for using interaction-based testing, because you are
> concerned in the interaction, not the "state".
>
> If you don't have integration tests then using an interaction-based
> testing approach is not worth it because you need something that is
> going to test the real objects working with real objects. In Rails you
> can write integration tests as Rail's
> ActionController::IntegrationTests, Rail's functional tests, RSpec
> stories, or RSpec controller tests w/view isolation turned off.
>
> IMO, one false benefit of only using a state-based approach when
> writing a full-fledged application is that every object is essentially
> an integration test at some level. You are always testing everything
> with everything that it touches. This can lead to having one failure
> in one model make several other model tests fail, and it can make
> several controller tests failing (as well as any other object which
> touches the model that is failing). I see this has a big negative
> because it makes it more difficult to pinpoint the issue. People will
> end up tracking it down, but it can be time consuming and frustrating.
>
> Now on the flip side people will complain that they renamed a model
> method and re-ran all of their tests and everything passed, but when
> running the application a bug exists. Doh, we forgot to update the
> controller that relied on calling that model method. It is normal to
> say/think, "well that should have failed because the method doesn't
> exist on the model". (It sounds like David Chelimsky may have
> something in trunk to help with this.) The main problem here though is
> that an integration test didn't fail exposing that you weren't done
> with your change.
>
> Thinking back to coordinating objects, my controllers don't contain
> business logic in them because they are application layer classes,
> they aren't apart of the domain of my software. They are only used by
> the application to allow the software to fulfill the requirements of
> my customer. Controllers are coordinators, not DOERS. They ask other
> objects to fulfill a business requirement for them like moving stocks
> from one portfolio to the another. So I used interaction-based testing
> here to ensure that my controller is finding a stock, finding a
> portfolio and asking a portfolio manager to move the stock to the
> designed portfolio. I don't need to have those things written or even
> fully implemented to ensure my controller works as I expect. I should
> be able to see that my controller does what it should be doing, even
> if the pieces it will use to do the work in the application aren't
> finished. Now if those aren't implemented I should have an integration
> test which fails showing me that the feature for moving stocks from
> one portfolio to another is not completed, but that isn't what I'm
> testing in my controller.
>
> Also after my controller works as expected I can go make sure the
> PortfolioManager works as expected, and then I can go down and make
> sure the Stock model does what I expect. When these objects are
> working correctly individual I run my integration tests to ensure they
> work well together.
>
> Another drawback of only using state-based testing is that you always
> have to develop bottom up. You have to start with the low level
> components and work your way out. I used to write code this way. I
> think I have progressed beyond that, and now I write things in a
> Acceptance Test Driven Development style. I start by writing an
> integration test from the user's perspective proving that the feature
> doesn't work, and then I move to the view, and then to the controller,
> then to any manager/factory/presenter/service objects that are
> required, and then down to any domain level objects (models and
> non-models alike). You can't do this approach with state-based testing
> only. There is a lot of value that can be gained by developing
> software this way.
>
> In short: Interaction-based testing allows you to ensure that an
> object is doing what you expect, without the underlying implementation
> having to exist yet at all or in full. It is great for application
> layer objects which typically only coordinate domain layer objects
> where the correct interaction is what is important. It also helps you
> develop interfaces, and it can scream loudly when you have an object
> doing way too much.
>
> * "Blaming "brittleness" of tests upon interaction-based testing is a
> red herring. Both interaction-based tests and state-based tests become
> brittle if they make assertions upon implementation details and overly
> constrain the interfaces between modules." - Nat Pryce
>
> * http://nat.truemesh.com/archives/000342.html - a wonderful read on
> interaction-based vs state-based testing
>
> --  
> Zach Dennis
> http://www.continuousthinking.com
> _______________________________________________
> rspec-users mailing list
> rspec-users@rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users


A lot of what you say makes me wish I was more experienced in this  
department :)  I am very new to this!  A part of me wishes I had the  
knowledge to write in the order of story -> view spec -> controller  
spec -> model spec.  However most of the time (I emphasize MOST) I  
don't have the foresight to do that.  The problem I'm trying to solve  
is almost always too complicated for me to know right away where to  
really start (my latest client has some crazy ideas).  Maybe the  
problem is that I make things too complicated for myself :)  However I  
have been a developer (just not using RSpec) for a very long time so I  
know fairly well how to recognize when things need to be complicated  
and when they don't.  This means .should_receive is often out of the  
question because I have no idea what the model should receive!

My primary concern when writing my specs that are to cover complicated  
features is that I do NOT want false confidence.  If I write a spec,  
and it passes, I want that to mean it works in my app.  When the spec  
goes green, my next step is to go hit Refresh in my browser.  If it  
doesn't work in my browser, then in my opinion, my spec is crap.  It's  
telling me things work when they don't.

I hear the concern being voiced that if you break one thing and 15  
specs pass then you're not mocking enough.  Well since this is BDD,  
after all, then we should be working closely to the current spec we're  
trying to make pass.  I change code, and 15 specs break, well I have a  
good idea of what code got broken because it's the top-most file in my  
editor!  I hit save, Autotest screamed at me, I'm going to go hit undo  
now.

Sometimes I make noob decisions and give model A a certain  
responsibility when it should have been done by model B.  I get it to  
work in the short term, my spec's pass, but later I need to add  
another feature and realize that old responsibility needs to be moved  
from A to B.  Now I have a red spec and X green specs.  I move that  
responsibility, X specs are green, with still the same 1 red spec.  I  
implement the new feature, X+1 green specs.  I refresh in my browser,  
sure enough, it all works.  I didn't have to go change all of my stubs  
and should_recieve's everywhere that just got moved.  There's no need  
to, because my specs cover the true business logic behavior, and not  
the model-specific "behavior".

While I do certainly believe the ability to spread everything out well  
enough so that 1 error = 1 broken spec comes from great wisdom and  
experience, I certainly don't have it, and I don't want to encourage  
others like me to try to strive for that because I don't know how to  
teach them from my own example.  What I do know, is that I use a lot  
of real models, and I don't spend any time fixing specs that are  
broken by working code.  I did that on my last project and it, in my  
opinion, wasn't worth it.  I'd change a variable assignment with  
a .save to a .update_attribute and then I had a broken spec.

My fear is that I'll write green specs, pat myself on the back, and  
then my company loses money because the site has bugs that my specs  
show green for because I don't have enough integration tests or  
whatever.  But I don't want to have to double my tests for the same  
amount of coverage.  I should have 1 spec for 1 feature and when that  
feature is working, that spec is green, plain and simple.  I admit I  
may be ignorant to a lot of the power behind RSpec, but I like this  
level of simplicity and straightforwardness.

Glenn Ford
http://www.glennfu.com
_______________________________________________
rspec-users mailing list
rspec-users@rubyforge.org
http://rubyforge.org/mailman/listinfo/rspec-users

Reply via email to