Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What failure modes can TDD leave behind?

Tags:

tdd

Please note I have not yet 'seen the light' on TDD nor truly got why it has all of the benefits evangelised by its main proponents. I'm not dismissing it - I just have my reservations which are probably born of ignorance. So by all means laugh at the questions below, so long as you can correct me :-)

Can using TDD leave yourself open to unintended side-effects of your implementation? The concept of "the least amount of code to satisfy a test" suggests thinking in the narrowest terms about a particular problem without necessarily contemplating the bigger picture.

I'm thinking of objects that hold or depend upon state (e.g. internal field values). If you have tests which instantiate an object in isolation, initialise that object and then call the method under test, how would you spot that a different method has left behind an invalid state that would adversely affect the behaviour of the first method? If I have understood matters correctly, then you shouldn't rely on order of test execution.

Other failures I can imagine cover the non-closure of streams, non-disposal of GDI+ objects and the like.

Is this even TDD's problem domain, or should integration and system testing catch such issues?

Thanks in anticipation....

like image 703
Neil Moss Avatar asked Aug 31 '10 13:08

Neil Moss


3 Answers

Some of this is in the domain of TDD.

Dan North says there is no such thing as test-driven development; that what we're really doing is example-driven development, and the examples become regression tests only once the system under test has been implemented.

This means that as you are designing a piece of code, you consider example scenarios and set up tests for each of those cases. Those cases should include the possibility that data is not valid, without considering why the data might be invalid.

Something like closing a stream can and should absolutely be covered when practicing TDD.

We use constructs like functions not only to reduce duplication but to encapsulate functionality. We reduce side effects by maintaining that encapsulation. I'd argue that we consider the bigger picture from a design perspective, but when it comes to implementing a method, we should be able to narrow our focus to that scope -- that unit of functionality. When we start juggling externalities is when we are likely to introduce defects.

That's my take, anyway; others may see it differently.

like image 86
Jay Avatar answered Dec 17 '22 18:12

Jay


TDD is not a replacement for being smart. The best programmers become even better with TDD. The worst programmers are still terrible.

The fact that you are asking these questions is a good sign: it means you're serious about doing programming well.

The concept of "the least amount of code to satisfy a test" suggests thinking in the narrowest terms about a particular problem without necessarily contemplating the bigger picture.

It's easy to take that attitude, just like "I don't need to test this; I'm sure it just works." Both are naive.

This is really about taking small steps, not about calling it quits early. You're still going after a great final result, but along the way you are careful to justify and verify each bit of code you write, with a test.

The immediate goal of TDD is pretty narrow: "how can I be sure that the code I'm writing does what I intend it to do?" If you have other questions you want to answer (like, "will this go over well in Ghana?" and "is my program fast enough?") then you'll need different approaches to answer them.

I'm thinking of objects that hold or depend upon state.

how would you spot that a different method has left behind an invalid state?

Dependencies and state are troublesome. They make for subtle bugs that appear at the worst times. They make refactoring and future enhancement harder. And they make unit testing infeasible.

Luckily, TDD is great at helping you produce code that isolates your logic from dependencies and state. That's the second "D" in "TDD".

like image 38
Jay Bazuzi Avatar answered Dec 17 '22 20:12

Jay Bazuzi


The concept of "the least amount of code to satisfy a test" suggests thinking in the narrowest terms about a particular problem without necessarily contemplating the bigger picture.

It suggests that, but that isn't what it means. What it means is powerful blinders for the moment. The bigger picture is there, but interferes with the immediate task at hand - so focus entirely on that immediate task, and then worry about what comes next. The big picture is present, is accounted for in TDD, but we suspend attention to it during the Red phase. So long as there is a failing test, our job is to get that test to pass. Once it, and all the other tests, are passing, then it's time to think about the big picture, to look at shortcomings, to anticipate new failure modes, new inputs - and write a test to express them. That puts us back into Red, and re-narrows our focus. Get the new test to pass, then set aside the blinders for the next step forward.

Yes, TDD gives us blinders. But it doesn't blind us.

like image 43
Carl Manaster Avatar answered Dec 17 '22 19:12

Carl Manaster