Wednesday 7 May 2014

TDD is not Dead. But, it shouldn't be evangelized either.

There’s been a lot of hoo-haa recently on the value and the future of TDD. DHH put out his controversial, blunt and somewhat rant-based blog post about a week ago.

This topic interests me a lot for a number of reasons.
  1. I come from a strong unit testing background. I’ve practiced it in several different flavors and discussed it philosophically with a lot of different types of programmers.
  2. I recently left a world where TDD was the defacto approach and have found myself in a team where it isn’t.
This blog post is going to review DHH's article, highlight weaknesses in it and provide counter arguments (with supportive evidence). Let's start with an overview of DHH’s post.

'TDD is Dead' Overview 

THESIS:
(shortened and paraphrased - please let me know if you think I got this wrong!): 

TDD is no longer needed as a practice in software development. Automated regression testing is all we need going forward.

ARGUMENT:
His argument is based on the following pros/cons of TDD.

Pros:
  • code coverage
  • confidence to make changes
  • driving code design
  • move towards automated/regression testing
  • training wheels for thinking about why/how to test code/software
Cons: 
  • guilt when not doing it
  • overly complex objects/indirection to avoid things that are ‘slow’

I really did scour this article for more cons… but I couldn’t find them. Let's go back to his thesis. Regression testing is enough to achieve all the pros you get from unit testing and should also alleviate the cons.


Weaknesses in DHH's Post


I think the primary issue with DHH's argument and evidence is that the claim that the pros you get by practicing TDD can be achieved with regression tests. Because, based on my experience... they can't. 
  1. Code coverage. 
  2. CAN - Arguably a different kind of code coverage however.. that being you don't break functionality.. designs can still be broken and not covered by unit tests
  3. Confidence to make changes
  4. CAN - Again, confidence your changes don't break functionality.. changes can still break design paradigms
  5. Driving well designed code
  6. CAN NOT - This is the kicker for me and where most of my argument lies. I'll elaborate on this next. Regression tests can't drive code design.
  7. Moving towards regression testing
  8. N/A - if you’re replacing your unit tests with regression tests, you’ve already moved towards and seen the value in regression testing
  9. Training wheels for thinking about why/how to test software
  10. SORT OF - This is really a deeper exercise than can be achieved with regression tests.. they go a short way in this but not the whole way


What's the Problem?


My experience in software development has allowed me to work on a vast array of different types of teams and software projects. Most of my work has been on teams and projects that were TDDed with a high level of code coverage and normally a suite of regression/smoke tests to back it up.

About 18 months ago, I moved out of this world and happened to find myself in the world of little to no unit tests.

I have found that this causes problems in your codebase that I did not see before on such scales: 
  • Little to no consistency in code design
  • Disregard (or lack of knowledge) for the single responsibility principle.
  • No shared understanding of how to approach common problems
  • A massive fear to change anything that has gotten into a particularly confusing state

I've identified a number of characteristics of code that has not been TDDed and are indicators of the above problems.
  1. Huge 1500+ line classes that do so much it’s hard to know what it is they ARE actually doing. (This is really the elephant in the room when it comes to crappy codebases.)
  2. An unreasonable large number of singletons (language permitting)
  3. No dependency injection
  4. Circular dependencies
  5. Huge, nested ifs, switches, whiles, fors.. etc...

These problems and the impact the leave on your code can't be addressed with regression tests. I do admit that TDD is not the only answer in addressing these. But, I will argue that in many cases it is a darn good one.. 


Why TDD is Not Dead


A well designed codebase respecting single responsibility and implemented in a consistent way is a pleasure to code in. You get things done quickly and efficiently. You're easily able to onboard new people and get them moving around it quickly. So, how can you avoid the above problems and achieve this?


Imagine a Project....


Lets assume that you are an excellent programmer who knows how to avoid the problems outlined above without TDD and unit tests. (Because you are, right?!!) Now, let's imagine the wide variety of different codebases and teams you might find yourself in throughout your awesome career.


One Person Team/Small-Medium Codebase

If you happen to be the only author of a small to medium-sized codebase.. you can probably write something beautiful and relatively stable without TDD. This is a strong candidate for skipping these techniques. Though I've found myself in the situation and I still practice TDD occasionally when I'm having trouble designing a particularly complex area of the codebase.


Small Team/Small-Medium Codebase

Maybe you're just a few people. You're all rock stars and you communicate effectively like bumble bees in hive serving your queen. (That queen representing an awesome codebase.) Maybe here you can get away without TDD as well. Perhaps.. but what happens when the team changes? New people are brought in? Communication drops? Guards are dropped, confusion sets in, there's no safety net.. Ahhhhh! 

I think these are the codebases that start out well, and have the best intentions... but if left running long enough, generally result in the negative codebase characteristics mentioned above.


Large Team/Large Codebase

The larger a team and codebase get, the harder it is to keep things clean. It's just the way of the programming world. Consistency is increasingly hard to maintain. You've got team members shifting on and off, writing code in areas they perhaps don’t understand. This is how you develop monster 1500+ line classes.


TDD is a tool to tackle these issues. How?:


Tests Drive Code Design

This is the single strongest reason outside of code coverage to practice TDD. This is also one of the pros that DHH mentioned about TDD.. and one that he didn't explain how it would be covered with a regression test suite alone. Write your tests before you code and you:

1. Expose your dependencies. 
2. Expose your expectations of what a class is responsible for.

What this results in that is like the whole point, is pain when writing your test if you have too many of either dependencies or responsibilities. This is an indicator that your class is getting too unwieldy and most often the easiest thing to do is to redesign to minimize dependencies or responsibilities. Wala! Single responsibility. Manageable, small classes. Boom. Bam.

Tests Document Your Code

Let’s face it. Documentation gets out of date. It’s also an excuse to leave behind confusing code. Tests act as a form of documentation for what your classes and their methods are responsible for. (Achievable by regression testing.. NO. Though regression tests do act as another form of documentation.) As with


Conclusion


As with everything, there's a balance to be had. And as with most contentious subjects there are those that operate on the extremes. I think the guilt DHH mentioned about how you feel when you don't TDD your code comes from those operating on the extreme side of 'TDD all your code, everytime'. It's impossible to do this and it's not expected. I think responding to this guilt with switching to the extreme other end of 'TDD is dead.. you don't need it ever' is not the appropriate response either. What is appropriate is thoughtful evaluation of each situation to decide if TDD's benefits would help bring about a better codebase and better software project.

No comments:

Post a Comment