Hacker News new | ask | show | jobs
by gfodor 4849 days ago
Over the years, I've grown less and less likely to write lots of tests, after being a very large test zealot during the peak of the TDD wave. There are a few reasons.

First, yes, TDD slows you down. The reason this matters is because a lot of our time as developers is spent exploring ideas, and it's pretty well understood that the faster you can get feedback on your ideas the easier it is to creatively develop them. In fact, the final result of your creative process can be completely different depending on the feedback cycle you have. From the micro to the macro perspective, religious TDD introduces an constant factor slowdown for small projects that yes, ends up being a net win in the long run if you live with the code for long, but is a net loss in the short run and also can prevent you from finding a solution to a problem since your creativity is stifled by the slow speed of idea development.

Second, when building web applications, building tests turns out to be fairly overrated. First, people will tolerate most bugs. (Write good tests around the parts they won't.) Second, if you have a lot of traffic, bugs will be surfaced nearly instantly in logs and very quickly via support tickets/forum posts. (What if there are bugs that don't make it into the logs and don't affect people? If a tree falls in a forest...) Much more important than mean time between failures is mean time to recovery. I'd rather have ten bugs that I fix within 5 minutes of them affecting someone than one bug that festers for a month. Not only because this is healthier for the code base in terms of building robustness, but also because human behavior is such that many fast bug fixes make everyone feel good but few lingering bugs make everyone miserable. People want to feel like you care, and are much more likely to feel cared for when you fix their problems quickly, and are not often interested in just how few bugs you ship if the one that you do has been affecting them for a month.

This isn't theoretical nonsense, it's a very real phenomenon where you use tests and manual testing to get up to a basic working prototype and then just throw it over the fence to flush out the bugs. It's the only way to do it anyway. (This only really works if you have traffic and can deploy changes quickly. To paraphrase a colleague, production traffic is the blood of continuous deployment.) Obsession over deploying bug-free software (an oxymoron anyway) is usually coming from people who haven't gotten over the fact that we don't need to ship software once a month or once a year but can do it every 10 minutes in certain domains. Instead of focusing on not shipping bugs, focus on shipping faster.

3 comments

I, on the other hand, have grown to love tests more and more.

Not for reasons of finding bugs – though that is often a nice side effect – but because once I have decent test coverage I don't need to look at the application anymore. Being able to run the tests in the background to verify my work is sane, while I move on to the next feature in parallel, I find, is considerably faster than the code/build/review cycle you find yourself in without a decent test suite.

My own performance, being a limited commodity, is the most important factor and I find tests help me increase output, not slow it down as you suggest. They are certainly not a panacea though. As always, use the right tool for the job.

I personally can't rationalize skipping the "review" part of the cycle. If it faces the customer, it's my responsibility and the luxury of not looking at it seems like trading away effectiveness for efficiency.
I see your point. I also take on design roles, so I naturally do that extra double take on the work during the design phases. Though I generally find the tests really do a great job of covering what they should.

If you are working with larger teams with designers and developers, I suppose it may not work out as well.

First of all I agree with the idea that automated testing is not a guarantor of quality. Rather automated testing is one tool, along with exception notification, logging, and a fail-fast philosophy that each shines a light from a different angle to help overall quality. And if you need the strongest possible guarantees against failure then you have to go with a more fundamentally strict language such as Haskell.

But I think your comment misses out on the middle ground in common dynamic languages. What a good test suite does is reflect on code to document the intention of the author and provide an automated means of verifying that that intention was in fact fulfilled. Granted, test suites have bugs too, so a passing test does not guarantee there wasn't a bug, but the fact that you have two (hopefully) orthogonal descriptions of the code in question means that you stand a much better chance of actually teasing apart what happened when something blows up down the line. I've been saved and emboldened by my test suite enough in Ruby that I now consider it irresponsible not to have complete-ish coverage. I just see it as good code hygiene.

I just see it as good code hygiene.

I think it is necessary hygiene for Ruby. In dynamic language web apps, everything is so loosely coupled together by a mixture of conventions and magic strings (or symbols, if that makes you feel any better about them) that it's very easy for one of the strings to break without noticing.

But IMO you are mostly (perhaps 80%) working around an inadequacy in the tools. It is not a virtue in and of itself.

I'm in the odd position of having transitioned to working at a startup, writing Backbone / RoR, after having been a compiler engineer, and before that, in ~2006, the author of a server-side web app framework designed to be strongly typed throughout (using a custom language for control binding to achieve this goal). RoR is very error-prone and a lot of work by comparison, especially when interfaced with Backbone so that the UI doesn't need constant whole page round trips.

So many bits and pieces need to be glued together, from attr_accessible through to update params slicing, binding controls in JS, JBuilder json templates, the whole game of finding just the right elements with jQ and friends, so much busywork with so many opportunities for mistakes to creep in - so that tests are absolutely critical.

You are conflating Ruby with RoR. You start by saying "I think it is necessary hygiene for Ruby." but then go on to say "RoR is very error-prone and a lot of work by comparison" and "So many bits and pieces need to be glued together, from attr_accessible through to update params slicing..." -- that's Rails you're talking about. And I don't blame you for that. As a long-time Ruby developer, I can say with no qualms that Rails is a not a very good web framework, way too buggy and full of magic. But please don't conflate that with Ruby. Ruby with Sinatra is a hell of a lot cleaner and it's much easier to structure your code well.
Over the years I have found that writing tests first is usually a faster way to get creative feedback. The reason is simple, the tests are easy to write, and they give you confidence that the feedback you are getting is accurate and not the result of something stupid.

Writing tests doesn't take much time since it's just a restatement of the code you already know you want to write. If you don't know what code you want to write, you can't write it. So you think about it. Once you know what you want to write, the test is trivial. Yet that trivial test let's you see the code execute, and gives you the confidence that the code you write was the code you _intended_ to write.

I agree with you about shipping faster. I don't agree with you about bugs. TDD can eliminate the more stupid of those bugs, while helping you go faster.