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:
// GetPersonByPersonGuid
public static PersonDTO GetPersonByPersonGuid(Guid PersonGuid)
{
SqlCommand command = GetDbSprocCommand("Person_GetByPersonGuid");
command.Parameters.Add(CreateParameter("@PersonGuid", PersonGuid));
return GetSingleDTO<PersonDTO>(command);
}
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.
This reads like a "i already do that so i don't have to do that" article - somewhat arrogant, and with some misconceptions/bad assumptions about TDD and BDD; you might want to read a bit more on the two, they are not the same.
ReplyDeleteAlso, the tests provide many additional benefits other than just forcing the developer to think about the interface first - regression tests, functioning how-to documentation, fine-grained profiling fodder, et al.
Finally, there is a significant different between thinking about the interfaces at the start of design, and actually implementing the test. The tests provide a reality check that your pseudo-TDD method does not have.
Otherwise enjoyed the article. Keep learning!
Hi Anonymous :) I'm not sure that I made bad assumptions about TDD but I have to say that you're right about TDD and BDD not being the same. I agree with your statements about the other benefits of testing too. However, I do think that the main point about TDD being a design methodology instead of a coding/testing methodology is valid. Besides, the hardcore TDD crowd can be a stiff bunch and it can be fun to give them something to get excited about.
ReplyDeleteNot so fast, Anonymous. Head over to the TDD essay on agiledata.org, and I think this exactly what Rudy is saying. In Part 7 of the TDD essay - "Because you think about the production code before you write it, you effectively perform detailed design". This is what I believe to be most effective about TDD, but there are some fairly large gaps which I've experienced. First, all developers must be on the same page as far writing tests at project inception (I've seen so many test written so much extra logic, you end up with flawed logic testing flawed logic). Joel Spolsky (Joel on Software) had an interesting observation which is, if you really have 100 percent code coverage, you will inevitably have code changes that result in a lot of your tests breaking http://www.joelonsoftware.com/items/2009/01/31.html. I've rarely worked on a project where I was lucky enough that my logic survived so _long_ that a test one week would even be the same next week or next month. No matter how small your iterations, your data sources, overloads, refactoring, etc. will impact your tests greatly if you've written them correctly. What's most surprising to me is that of all of the code samples I've downloaded, very few if any developers have bothered to also post their unit tests. I'm starting to see more examples of this, but this means that in large part, we are all running code no one has tested, or, we are all inventing different tests?!
ReplyDelete