The Failures of “Intro to TDD”
blog.testdouble.com
blog.testdouble.com
I guess my core problem is TDD only drives your design if you write quite granular tests but they then become barriers to further refactoring/redesign. However my current preferred approach (for anything non-trivial) is different to the authors. As he does I start out with outer-level acceptance-y tests, however those integration tests are then combined with extensive refactoring to drive the design, so I don't do the dreaming up of collaborators he does in step 4.
My reason for not thinking of collaborators early is although I think that approach has legs I found it didn't necessarily always the simplest/most elegant design you could come up with. I ended up encoding my first understanding of how to solve the problem into the interactions, even though I knew that my understanding of the problem at that point was much less than it would be once I dug a bit deeper.
Anyway with my current approach if I extract additional classes/whatever I might then test them directly, or use further tests to drive their design further (incl test doubles as appropriate). So I'll have one or a few tests of the extracted behavior at the outer level, and maybe more thorough testing at the lower level because quite often testing all the combinations and edge cases is easier at this level.
I find that most new developers instinct is to do exactly the opposite - i.e. write the first line of their main function, then realise it needs, say, an argument-parsing class, so start writing that, then realise that needs a logging class, so start writing that, etc. which means you have to keep much more stuff in your head at once, you end up writing much more code in each commit than you should, and the distinction between bits of code at different levels of abstraction often ends up much blurrier which leads to messier design.
This seems to be quite easily countered though - just pointing out to people the difference between breadth-first and depth-first styles can have quite an immediate effect
As a side note, I find it helpful to think of what the high level business objects will be at the beginning, but build a bunch of utility functions from the bottom up, working towards a DSL of sorts at the various abstraction levels. As you explore the solution, the end location of those functions usually becomes abundantly clear.
Yeah I think this is a reasonable approach, especially as if you do end up completing this, and get to a design you are happy with, it will probably be very different (and hopefully infinitely superior) to your original high level design.
You've probably already heard of it but the mikado method can also be useful in some of these situations.
[0] http://www.infoq.com/interviews/mikado-method-restrurcture-s...
Another issue with TDD is that it is incredibly difficult to reason about. In many groups, there are at least one developer sold on the concept, and when I voice my concerns with the methodology they immediately demand an explanation why I "hate testing", or alternatively preach about all the goodness of unit testing. Which of course isn't the issue at all. Many developers seem to confuse TDD and unit tests, and the supposed benefits of the former is very hard to quantify.
At my startup we're indirectly tackling difficulties associated with unit testing. Our tool Alive [0] is an interactive programming extension to Visual Studio that made me really enjoy writing tests first and then implementing the features - when with each keystroke I get to see what the code does.
Eventually you spend more time thinking about testing than you do actually getting shit done. You have to because otherwise you find yourself rewriting tests every time you refactor. You rationalize this time under the guise of "it's making me think more clearly about my design." Once it starts wearing thin disillusionment takes root. The first step towards my own enlightenment was when I realized that I needed tests to help me ensure that my test framework was working. What's testing those tests? Tests are code, and code needs to be tested. Where does it end?
I wrote my own toy test framework as an exercise. I was trying to * really * wrap my head around meta-programming, so I meta-programmed my test suite to test the heavily meta-programmed data classes. It solidified into a grotesque mush and I scrapped the whole thing. Now I'm solving the original problem with boring old Rails and sanity has been restored.
Now when I start a project I do it knowing that my code is going to suck and I'm going to refactor it over time. The truth is, when there's no tests I only have to refactor one code base and not two. I don't need to learn two frameworks. I don't need to understand two domains. The amount of time I've spent maintaining code has sharply diminished after I stopped being so religious about testing. If I don't know what it's doing, the REPL is my best friend. Backtraces rule.
If your code is Serious Business, like, say, SQLite which is used everywhere, a robust test suite is a very nice tool to have and maintain. For everyone else, it's another step on the road to mastery.
Also if you're using a dangerously unsafe language like C, tests can alert you to brewing problems. If you're using a safe language solving not-so-hard problems, a test framework is just adding complexity to paper over your lack of experience.
At best they can give a person with good judgement a different angle to look at their problems with.
At worst they are thought-stopping slogans that turn a gullible novice into a crippled novice.
I've run teams where a very agile approach made sense (usually where the dev and users where small in number and very close) and others where a more formal phased approach made sense (usually where we need co-ordination across companies).
The simple fact is that trying to run these types of programme the same way is an exercise in futility. That's not to say that one can't extract common practices that make projects generally better e.g. it's generally preferable to get code into the wild sooner rather than later if you can do it safely, it's just that the "one true path" idea is a marketing concept not an engineering one.
Deleted comment
The scientific method is ill-equipped to formally prove a generalized theory of software development. There are too many significant variables at play; too many equations to solve. We are left with little but "In my experience..." to guide us (as well as local experimentation, where science can actually be of some help).
But it seems people have a tendency to make them into ideologies/religions that when applied correctly will solve everything. I guess it's a way to exercise power over people. No individual thought allowed.
I think that's a preferable approach for complex systems, the "redundant tests" issue is not a problem and end to end tests are reduced to just a few proving everything is hooked up.
I've designed systems in this way before (strangely, they always seem to be some sort of document validation), and they always turn into complete hairballs of code that eventually gets thrown away and something much simpler substituted.
Now, I just stick to DRY, YAGNI, and "Compression-Driven Development" http://mollyrocket.com/casey/stream_0019.html
At least they won't have to change the acronym.
As a very general statement, I'd say any developer should try their hardest covers the basic tenets of TDD while working. Let me back that up, since I'm sure that's probably rubbed people up the wrong way.
1. I know what the system is currently doing
2. I know what the system should be doing
3. These things are different, and I know how to check
4. I change the system
5. I check that the system is now doing what it should be doing
That all seems pretty uncontroversial, I hope.
1. If you don't know what the system is doing now, you can't tell if there's anything to do at all.
2. If you don't know what it should be doing, there's no way of implementing it.
3. If you can't check that if it's doing the right thing (even manually) then you're fairly screwed (although we've all ended up in a case where the "check" is "ask the customer afterwards if it's fixed").
4. You obviously need to actually do something to change the system.
5. Finally you should check your work actually, well, works.
The idea that you have an automated check for 3 seems like a fairly good idea, as long as it's not too onerous. Then suggesting that it's implemented and run before 4 makes sense, it's unlikely to be much harder to do it before rather than afterwards and it gives me some confidence that the test checks what I think it does.
Personally, while I quite like the workflow presented, I'd do most of that on paper / whiteboard / in my head. Take the task and break it down into smaller things it needs to do. Don't rush into creating the right structure, just actually understand what it is this thing will need to do. Then I can consider general designs, where the edges will be, types of failures and errors that are likely to pop up and I can write something (as always, discovering more as it's written and having to change things). It's very useful to then start on the bits I'm less sure about, or the ones that will have the most impact if I have to change things. I think that gets easier the more you code though.
However there are types of programs which aren't well covered by a test (demo) or set there of.
Sometimes the program is being written to learn a value which is unknown, or for which a simplified example having success insufficiently tests the validity or scalability of the software.
Tests in those cases may work at a unit level, but still provide insufficient insight in to the soundness of the overall results.
InvoiceFetcher.FetchInvoice()
PaymentApplier.ApplyPayment()
just seems terrible.The important thing that TDD or any other software design methodology is trying to accomplish is getting you to think about the design of your programs. However you want to accomplish that is fine as long as you take some time before you open your editor and think before you start writing code. It doesn't necessarily have to be TDD.
I think that thinking about the design of my programs is easier once I have some amount of working code.
There's a minimum organization needed for starting to code, but every time I decide to think any further when solving a problem I'm not used to (optimally that would be always), I end up optimizing for the wrong problems and have to restructure it all later anyway.
http://michaeldehaan.net/post/120522567217/the-case-for-test...
To be frank, when you said the audience was 'typical enterprise java developers', it seems that you are already describing a group of developers who are sort of 'stuck' in their ways, who maybe haven't heard of other things like TDD and WOULD necessarily miss the point of a lot of it.
Just sayin - it could be helpful to avoid broad statements like "never start with TDD" that may be more audience specific.
Can you make the code say what you mean?
TDD is just really efficient at demonstrating your inadequacy at achieving this goal. It's a really uncomfortable experience, and to get comfortable with that feeling takes a certain acceptance of the human condition that reads like something straight out of eastern philosophy.
Tl;dr to err is human. To really fuck up requires the aid of a machine.
Many businesses want to treat developers as a fungible commodity and believe some magical process will enable that. It makes sense financially but I've never seen it actually work.