Hacker News new | ask | show | jobs
by ArbitraryLimits 4531 days ago
> When TDD isn't a good idea (say, when ... you don't yet know how you want to structure the system)

(Apologies in advance as I can't figure out how not to sound snarky here.)

Isn't that called "the design? And is there any meaningful way in which, if "test-driven design" fails if you don't already have the design, it's worth anything at all?

3 comments

Sure, you can call that structure the design, or the architecture, or whatever you like. Either way, it's a fair question.

As a point of semantics: TDD generally stands for "test-driven development," not "test-driven design," though the article here does make the claim that TDD helps with design.

To reduce my personal philosophy to a near tautology: if you don't design the system to be testable, it's not going to be testable. TDD, to me, is really about designing for testability. Doing that, however, isn't easy: knowing what's testable and what's not requires a lot of practical experience which tends to be gained by writing a bunch of tests for things. In addition, the longer you wait to validate how testable your design actually is, the more likely it is that you got things wrong and will find it very painful to fix them. So when I talk about TDD myself, I'm really talking about "design for testability and validate testability early and often." If you don't have a clue how you want to build things, TDD isn't going to help.

If you take TDD to mean strictly test-first development . . . well, I only find that useful when I'm fixing bugs, where step 1 is always to write a regression test (if possible). Otherwise it just makes me miserable.

The other thing worth pointing out is that design for testability isn't always 100% aligned with other design concerns like performance, readability, or flexibility: you often have to make a tradeoff, and testability isn't always the right answer. I personally get really irked by the arguments some people make that "TDD always leads to good design; if you did TDD and the result isn't good, you're doing TDD wrong." Sure, plenty of people have no clue what they're doing and make a mess of things in the name of testability. (To be clear, I don't think the author here makes the mistake of begging the question: I liked the article because I think it honestly points out many of the types of mistakes people make and provides a reasonable approach to avoiding them.)

I think you're spot on here - TDD is great as long as you're not too obstinate about it. It's a trade off, just like every interesting problem.

One point I'd like to draw out. If you don't have a clue how you want to build things, TDD isn't going to help.

This is exactly right. If you find yourself completely unable to articulate a test for something, you probably don't really know what it is you're trying to build. I think that's the greatest benefit to TDD: it forces you to stop typing and think.

Exactly. This is the whole purpose behind the "spike" - make a branch, write a crap implementation of some code to help understand the problem, put it aside. Then go write the production version TDD style. Once you understand the problem, you can use TDD to create a good design to solve that problem.

Sounds crazy, but this is how I do everything I don't understand. And my second implementation is usually better than my first.

Or, in the words of Fred Brooks, build one to throw away. I'm always amazed at how prescient he was.

Unfortunately I find all too often that spike project finds its way into production for one reason or another. Now I only spike in Befunge.

If you find yourself completely unable to articulate a test for something, you probably don't really know what it is you're trying to build.

I don’t buy this argument. How would you write tests to drive the development of a graphics demo, say rendering a Mandelbrot set? Or a tool to convert audio data from one format to another? Or any other kind of software where the output doesn’t consist of readily verifiable, discrete data points?

Are you asking about unit tests or acceptance tests?

The problems you describe are very high level, but we could design an acceptance testing scheme for them. For the Mandelbrot set it might involve comparison to a reference rendering, for the audio tool a reference recording. In both cases you'd allow a delta relevant to the application, and probably also benchmark for acceptable performance.

But my point was more aimed at unit testing. When you set out to write a function you should know something about that function before starting. If you know enough to write the function signature, you can first write a failing test. If you can write a bit of code in that function, you can write a bit expecting the behavior of that code.

Are you asking about unit tests or acceptance tests?

I suppose what I’m really asking is how you would go from not having software to having software that does those things, using TDD. I think in practice its fail-pass-refactor cycle is normally applied at the level of unit tests, but in any case, how would using TDD help to drive a good design, to ensure testability, or otherwise, in that kind of situation?

(I’m asking this rhetorically. I don’t think TDD is a very helpful process in this context. I’m just trying to demonstrate this with practical examples rather than bluntly stating it without any supporting argument.)

I think I mostly agree with your larger point, but I'm not in love with your examples. The Mandelbrot set does consist of readily verifiable discrete data points, after all. I don't have any problem imagining myself developing a Mandelbrot set program using TDD.

A great example for your point (which might have been what you were getting at with the audio thing) is a test for creating files in a lossy audio format. The acid test is if it sounds right to a human being with good ears, I've got no clue how you would write a pure computer test for that.

In my own work, a great example is finding a NURBS approximation of the intersection of two surfaces. There are an infinite number of correct answers for a given pair of surfaces, and testing that the curve you've generated fits the surfaces is a distressingly hard problem.

"Test-driven design", as it is commonly understood, does seem to be a mythical beast. I've hunted it with both logic and experience and come up empty-handed.

That said, i do still find that while test-driven development doesn't itself create good design, it is a useful tool to help me create good design. I have a bite-size piece of functionality to write; i think about what the class should look like; i write tests to describe the class; i write the class. The key thing is that the tests are a description of the class. The act of writing down a description of something has an amazing power to force the mind to really understand it; to see what's missing, what's contradictory, what's unnecessary, and what's really important. I experience this when i write presentations, when i write documentation, and when i write tests. The tests don't do the thinking for me, but they are a very useful tool for my thinking.

It's very common in software development to receive incomplete requirements. My world would be a very different place if I always receive feature complete design documents (and in same cases, any documents at all). Had I insisted on any kind of TDD, it would greatly increase my workload by reducing my ability to alter the design to accommodate new feature requests and changes while internal clients test the code.

I do gather some places do things differently though. Must be nice.

I think I'd have to offer that my experience differs. TDD is not at all big-design-up-front, even with this reductive exercise. In fact, most features start very minimally and the tree of dependencies grows over time, just like any system becomes incrementally more complex. TDD is just one tool (of many) to help manage that complexity. Both by offering some regression value (at least of the logical bits) and also by encouraging small, bite-sized units that are easy to make sense of (and therefore change or replace)