TDD isn't enough
There’s been a bit of a bru-ha-ha recently with Rocky Lhotka’s comments on TDD (which I’m listening to for a refresher). This touched off a few posts, but unlike the Microsoft got TDD wrong incident a few months ago, discussions are pretty level headed all around. Jeffrey Palermo had a good post to get things started and Rocky has followed up with an excellent post called Forests, trees, testing and design. All good stuff for those that are interested and juxtaposing positions of arguments is usually the best way to elevate awareness of something and introduce discussion. This one certainly has.
As I’m giving a session next week on TDD and SharePoint I thought I would stress the fact that TDD isn’t the be all and end all to development. Just like unit testing. Just like any practice. As Jeffrey Palermo stated “A comprehensive testing strategy is a must, and testers should have a good testing plan.”. Actually I would say developers should have a good testing plan. The line is slowly being blurred between developers and testers, just like it’s happening between developers and designers and it’s important to know what needs to be done.
I don’t subscribe to the TDD purists about 100% tests written for 100% lines of business code. That’s a nice utopia to think about, but there still is user acceptance testing, functional testing, etc. and some things that, while they might be able to be automated, can’t be realized up front. There’s a term in Agile called Big Design Up Front (BDUF). You’ve probably seen it before. The developer comes out with a giant UML/Class diagram with all kinds of classes and methods specified. As a guy that practices and tries to strive for a clean object design, I usually question a lot of classes when I see them. To the guy who made that big diagram it probably makes sense to him but when you pose the question to him (and this happened recently for me) they usually get to the point of saying “Well, I guess we really don’t need that set of classes”. With TDD you get this up front (or can) so I would much prefer to see my domain evolve as needed, based on requirements (rather than some developer saying we need this class and that class).
In any case, practising TDD and always writing tests first isn’t always enough. It’s a small part of a larger world. You do want to test your business functionality, but in a lot of applications I keep seeing there’s very little real “business logic” and more “access logic”, “transaction logic”, and “dependency logic”. Access logic is where you put some kind of rule in a business class getter like this:
5 public class Customer
6 {
7 private string firstName;
8
9 public Customer()
10 {
11 }
12
13 public string FirstName
14 {
15 get { return firstName; }
16 set
17 {
18 if(value.Length > 50)
19 throw new ApplicationException("First Name is too long.");
20 firstName = value;
21 }
22 }
23 }
So you do want a test to ensure that you’re not exceeding your string length here, but this is pretty simple and could be done using either TDD or building the object first (yes, the example is a bit lame because I probably wouldn’t throw an exception here and you could use attributes on the property instead, blah, blah, blah, blah). Is this business logic? Maybe, and it does need a test but probably only one will suffice and you certainly don’t need TDD to arrive at this.
Transaction logic is where you end up doing a lot of testing just to make sure something gets in one end and out the other. I see so many examples of this where someone passes a DataSet to a method, fills it, throws it back (or uses a ref object), lather, rinse, repeat. This is so non-OO it’s not funny so writing tests or calling this business logic is just plain silly. Yes, objects need properties set to be of any value (for the most part) but OO is about behaviour not data.
Dependency logic is the big, bad, wolf of the BDUF syndrome and if you create that giant class diagram, expect to see this a lot. Take a look at this test:
9 [TestFixture]
10 public class ActivityTestFixture
11 {
12 [Test]
13 public void PlannedManHours()
14 {
15 Activity activity = new Activity(new Template(new Job(), new Discipline()));
16 int expectedHours = 100;
17 int actualHours = activity.PlannedManHours;
18 Assert.AreEqual(expectedHours, actualHours);
19 }
20 }
It was for testing something and get some estimate of hours. The test looks REALLY ugly because as we fleshed it out (using TDD) it became immediately apparent that we needed to create 3 separate classes (in a certain order) just to get the object we really wanted to test. Not the best class design I’ve seen and something that looked okay on a big class diagram but when you got down to it, you realize that you have this crazy dependency between your classes and in order to test them, you need to create a giant hierarchy of objects. TDD would have brought this out at the forefront (or at least identified it early or helped with the design a little better).
Okay, so I got a little off track here but really it’s about balance. TDD by itself as a testing methodology or a design methodology just doesn’t work. It needs help from other things. Sometimes those things are class design, unit tests, etc. and they all balance each other in order for you to get your job done. Whether you use TDD or not is up to you of course but you need to use a multitude of things to balance it out. It’s like SharePoint. It’s a mish-mash of technologies as just one isn’t enough. Use what you need when you need it, and try to keep it level across your solution.
Now playing: Carl Franklin - CSLA.NET 2.0 with Rocky Lhotka