I’m taking a quick break from the High Performance Data Access Layer Posts to utter what some people will consider blasphemy about TDD. Test Driven Development is creating quite a buzz these days. One thing that I find interesting about Test Driven Development is that it’s not really about tests. If you have any doubts, listen to the TDD pundits. You’ll notice that many of them are trying to rename TDD to Behavior Driven Design. Why would they do that? Because a lot of developers get hung up on the word “Test”. They assume that TDD is primarily a coding methodology that produces code with good unit tests and high test coverage. That’s missing the point. TDD is a design methodology that forces you to write better code by first thinking about how that code is going to be consumed, and doing that doesn’t require tests.
For anyone who isn’t familiar with TDD, the idea is simple. Before you write any code for your application, you first write an automated unit test. If you want to write a C# method that returns the highest number from an array, you first write a unit test that creates an array of numbers, passes it to your method (which hasn’t been written yet), and then checks the value that your method returns to make sure it is the highest number. Since you haven’t written the method yet, the code fails. This failed test then drives your development of the method. When you get the method working correctly, the test will pass. The idea is that every piece of code you write is driven by a failing test. This practice results in very testable code, high test coverage, and it prevents you from writing a lot of extraneous code that is never used. But it also does something else that is even more important. By making you write tests that consume your code first, TDD forces you to think about how your code is going to be consumed on a very fine grained level. This is the real benefit of Test Driven Development, write consuming code first because it forces you to think about how your code will be used and will drive better code design.
That makes perfect sense to me. In fact I realized that I’d been doing TDD for years before I ever wrote a single unit test. If you’ve read any of my other posts, you’ve probably heard me say things like “Start at the end” or “the principle of thinking first about how we want to consume our code then writing code to that target”. That is really the essence of TDD, write your consuming code first. That doesn’t require the consuming code to be automated tests.
Here’s a quick example. In my post on High Performance Data Access we had a situation where we needed to create a PersonDb class that contained all of the data access methods for person data like GetPersonByGuid(), and we also needed to make a DALBase class that encapsulated our repeated data access logic like getting a connection string, getting a command object, etc. The normal way to approach that problem is to write the DALBase first, then write the PersonDb since PersonDb will be using the methods provided by DALBase. That approach would work, but it’s also how we wind up with API code that’s not as efficient as it could be. My approach was to write the PersonDb class first. This forced me to start by thinking about what code I wanted to keep out of PersonDb and what code should really go in my data access methods. What changes between each data access method? I came up with 3 things: the command name; the parameters list; and the return type. Since those are the only things that change, those are the only things that should go in my data access methods. Everything else that doesn’t change should be encapsulated behind DALBase. The resulting data access code looked like this PersonDb.GetPersonByPersonGuid() method:
public static PersonDTO GetPersonByPersonGuid(Guid PersonGuid)
SqlCommand command = GetDbSprocCommand("Person_GetByPersonGuid");
That is some simple data access code. Now if I had started by writing my DALBase first, the code I wrote would have worked fine. After all, I have written a DAL before. But, I don’t think it would have been this clean.
So, my conclusion is that TDD is a great DESIGN methodology, and when possible I think that writing automated tests first and using a full TDD process is probably the best way to do it. But, if you’re in an environment where that’s not possible, remember that the core idea of TDD is simply to write your consuming code first. This is a practice that can be used in any environment, even if you don’t use any automated unit tests. Try it and I think that you’ll find yourself addressing design problems earlier, and writing much cleaner code.