Live data from Hacker News

The Failures of "Intro to TDD"

blog.testdouble.com

11–20 of 85 posts

Re: The Failures of "Intro to TDD"

#11

I agree with the general approach suggested in the article (in tests, write/assume the code you wish you had). But one detail ran counter to my personal practice. I don't believe that "symmetrical" unit tests are a worthy goal. I believe in testing units of behavior, whether or not they correspond to a method/class. Symmetry leads to brittleness. I refactor as much as possible into private methods, but I leave my tes…

Symmetrical tests really help other developers on your team. It depends on your design, but "public API" tests are often something between integration and unit tests - i.e. they tests in-process co-operation of units.

Re: The Failures of "Intro to TDD"

#12
post #8

I agree with the general approach suggested in the article (in tests, write/assume the code you wish you had). But one detail ran counter to my personal practice. I don't believe that "symmetrical" unit tests are a worthy goal. I believe in testing units of behavior, whether or not they correspond to a method/class. Symmetry leads to brittleness. I refactor as much as possible into private methods, but I leave my tes…

I understand the concern, but I value consistency and discoverability, so symmetry of thing-being-tested to test itself is (so far) the best way I've found to make sure it's dreadfully obvious where a given unit's test is. This approach is not concerned with brittleness or being coupled to the implementation because each unit is so small that it's easier to trash the object and its test when requirements change than…

I suppose that if you do keep things that small, it could work well to trash and rewrite. Plus it has the benefit of making you consider explicitly what is going/staying.

Personally, I like my tests to be pretty clearly about the behavior of the contract, and not the implementation, which is hard when you require every method have a test.

I'd also be concerned that other team members are reluctant to delete tests - as this is a dysfunction I see often, and try to counteract with varying degrees of success.

Re: The Failures of "Intro to TDD"

#13
Yes! I've always hated the common kata, because for every dev writing software for a bowling alley, there are 200,000 devs writing software the sends invoices or stores documents.

When I'm teaching TDD, the kata I have everyone go through is a simple order system.

The requirements are something like:

A user can order a case of soda

The user should have their credit card charged

The user should get an email when the card is charged

The user should get an email when their order ships

If the credit card is denied, they should see an error message

(etc....)

This way they can think about abstracting out dependencies, an IEmailService, a ICreditCardService, etc. There are no dependencies for a Roman Numeral converter.

Re: The Failures of "Intro to TDD"

#14
OK but after you "Fake It Until You Make It" and you have to add a new feature to that class structure, aren't you just going to start over with all the failures he brings up?

---------

I haven't designed code the way he's advocating, but I have attempted TDD by starting with the leaves first. Here are the downsides to that:

1) Sometimes you end testing and writing a leaf that you you don't end up using/needing.

2) You realize you need a parameter you didn't anticipate. EG: "Obviously this patient report needs the Patient object. Oh crap I forgot that there's a requirement to print the user's name on the report. Now I've got get that User object and pass it all the way through".

Maybe these experiences aren't relevant. As I said, I haven't tried to "Fake It Until You Make It".

Re: The Failures of "Intro to TDD"

#15
post #3

The more I try to explain TDD, the more I realize that some of my favorite concepts, like the ability to mock functionality of an external process because the details of that process should be irrelevant...is just beyond the grasp of most beginners. That is, I thought/hoped that TDD would necessarily force them into good orthogonal design, because it does so for me...but it seems like they have to have a good grasp o…

Test Driven Design doesn't fundamentally solve any problems, it's a tool for master craftsmen to tease out subtle errors in their design. The problem is junior programmers can't recognize bad design so they end up writing tests for a bad design, because they don't understand how bad the design is, they don't understand how to break it.

IMHO junior programmers tend to think that over specifying a design helps them, only a master can recognize the brilliance of something like SMTP/REST/JSON over X400/SOAP/XML. TDD just helps them over specify their bad designs.

That said TDD is a wonderful tool in the hands of a master. It's like photography, a $10,000 camera won't help you solve your composition problems. Tech can help ensure Ansel Adams doesn't take a photo with the wrong focus, but a properly focused poorly composed image does not a masterpiece make.

Re: The Failures of "Intro to TDD"

#16
> ...TDD's primary benefit is to improve the design of our code, they were caught entirely off guard. And when I told them that any regression safety gained by TDD is at best secondary and at worst illusory...

Thank you! Details of this post aside, this gave me an Aha! moment and I feel like I'm finally leaving the WTF mountain.

Re: The Failures of "Intro to TDD"

#17
The approach outlined actually makes much more sense without OO. I guess the WTF comes from forcing yourself into a world of "MoneyFinder", "InvoiceFetcher", etc. Makes it look a lot more complicated and prone to error than it is, because you're now supposed to mock objects that may have internal state. Otherwise it's the usual top-down approach with stubs.

Re: The Failures of "Intro to TDD"

#18
post #6

Might one of the problems be that we place too much importance on the "symmetrical" unit test. In your example the child code is still covered when it is extracted from the parent. As a developer that often prefers tests at the functional level, the primary benefit of tests for me is to get faster feedback while I am developing.

The trouble with abandoning symmetrical unit tests is that:

* The unit is no longer portable and can't be pulled from the context it was first used in (e.g. into a library or another app) without becoming untested. And adding characterization testing later is usually more expensive * A developer who needs to make a change to that unit needs to know where to "test drive" that change from, which requires that they know where to look for the parent's test that uses it. That's hard enough but it completely falls over when the unit is used in two, three, or more places. Now a bunch of tests have to be redesigned and none of them are easy to find. * Integrated unit tests like this lead to superlinear build duration growth b/c they each get slower as the system gets bigger. This really trips teams up in year 2 or 3 of a system.

Re: The Failures of "Intro to TDD"

#19

The approach outlined actually makes much more sense without OO. I guess the WTF comes from forcing yourself into a world of "MoneyFinder", "InvoiceFetcher", etc. Makes it look a lot more complicated and prone to error than it is, because you're now supposed to mock objects that may have internal state. Otherwise it's the usual top-down approach with stubs.

Yep. I don't really practice "OOP" anymore because each of my objects are really just behavior with no application state (their only state would be the other behavioral objects they depend on).

However, in a classical language it's easier to organize stuff into classes and for the purpose of a post like this one it's easier to convey. But you're dead on.

Re: The Failures of "Intro to TDD"

#20

The approach outlined actually makes much more sense without OO. I guess the WTF comes from forcing yourself into a world of "MoneyFinder", "InvoiceFetcher", etc. Makes it look a lot more complicated and prone to error than it is, because you're now supposed to mock objects that may have internal state. Otherwise it's the usual top-down approach with stubs.

Yeah I think it's interesting that the final approach with "logical units" and "collaboration units" mirrors a functional approach with "functions" and "higher-order functions". The advice to write small "logical units" could also just be "write pure functions". The complex class hierarchy in the final example could probably be avoided entirely if you were using a language with first class functions. As a bonus, in a functional language the "collaboration units" have probably already been written and tested for you.
Post reply on HN