Rocky Lhotka on TDD, Take #2

time to read 7 min | 1226 words

Rocky took the time to clarify his comments on TDD in dotNetRocks #169. There seems to be a couple of main points there:

As a testing methodology TDD (as I understand it) is totally inadequate... ...the idea that you’d have developers write your QA unit tests is unrealistic

TDD is not a QA methodology. TDD is about designing the system so it is easy to test. This makes you think about what the code does, rather how it does it. The end result is usually interfaces that are easier for the client to work with, coherent naming for classes/interfaces/methods, the level of coupling goes down and the cohesion of the system goes up. Then you run the test.

The idea in TDD is not to catch bugs (although it certainly helps to reduce them). The idea is to make sure that developer think about the way they structure their code before they start writing it. It also means that developers has far more confidence to make changes, since they have a test harness to catch them if they break something.

And it was pointed out on Palermo ’s blog that TDD isn’t about writing comprehensive tests anyway, but rather is about writing a single test for the method – which is exceedingly limited from a testing perspective.

You start with a single test, to check the simple case, then you write another test, for a more complex case, etc. The purpose is to assert what you are going to do, do it, and then verify that it does what you assrted it should. QA isn't going away in TDD, for the reasons that Rocky mention in his post (developers rarely try to break their app the way a Tester does). QA and Developers takes two different approaches. When I get a bug from QA, I write a failing test for this bug, and then I fix it. Now I got a regression test for this bug.

When I touch this part in 6 months time, I can do it with confidence. I know that I will not:

  • Break any of the assumtions of the original developer - I got tests that verify that the original functionality works
  • Regress any bugs that were found before - I got tests that would verify the bug is fixed.
  • Any future maintainer of the code will not break the sutff I'm doing now - I write tests that will verify that the new functionality is working.

Those and clear design are the main benefits fo TDD, in my opinion. Again, I agree that this doesn't remove the need to have QA team that hates the product and really want to break it :-).

I don't buy into TDD as a “design methodology" either. You can't "design" a system when you are focused at the method level. There’s that whole forest-and-trees thing... ...But frankly I don’t care a whole lot, because I use a modified CRC (class, responsibility and collaboration) approach.

CRC is cool, I agree, but the issue is how detailed you go. I can probably think of the major classes that I need for a project up front, and maybe even come up with their responsabilities and some of the collaborators. Until I sit down with the code, I can't really say what methods and what parameters will be at each class, what other stuff I need to do this work, etc. TDD is about the interface/class/method design, not directly about full system design (although it will influence the system design for sure).

And as long as we all realize that these are developer tests, and that some real quality assurance tests are also required (and which should be written by test-minded people) then that is good.
None of which [developer tests and QA tests], of course, is part of the design process, because that’s done using a modified CRC approach.

This is something I really do not agree with. If you don't use TDD to influence your design, you are not doing TDD, period. You may have tests, but it's not TDD. Writing tests after the fact means that you don't get to think about what the client of the code will see, or use things that make it much harder to test the class. You lose much of the qualities of TDD.

What you end up with is a bunch of one-off tests (even if they are NUnit tests, mind) that doesn't cover the whole system, and are only good for testing very spesific things. They don't affect the design of the code, which can lead to very cumbersome interfaces, and they don't cover the simple to complex scenarios. They may represent several hours (or days!) of programming effort that culimate in a long test that test very spesific scenario. Those are integration tests, and while they have their place, they are not Unit Tests. NUnit != Unit Tests.

And here is a reply to some of the comments in the post:

Sahil Malik commented:

TDD does_not reduce your debugging time to zero. You do end up with an army of tests to maintain. Since the tests are written by and maintained developers, you cannot go with the assumption that the tests themselves aren't bug free. So you write tests to check your tests? Inifinite loop.

You do not stop debugging, that is correct. It does mean that you have a very clean arrow that points to where it failed, including the details, so you have much easier time to fix it. And having an army of tests to maintain would be bad if they didn't have an army of code that they test. Yes, tests has bugs, and the code has bugs, but the chance that you would get the exact same bug in both the test and the code is not very high. When you will see the test fail, you'll investigate and discover that the code is correct and the bug is in the test. No need to write TestTheTestThatTestTheTest... scenario.