I’ve known about TDD for quite a while now. My first introduction was when I left Caterpillar and became the only Windows developer at my new job. I was used to having an entire team do regression testing before we released our software. I wasn’t afforded that luxury so I started looking at what I could do to help with my testing. I found a blog or two about TDD, and my first reaction was somewhat skeptical. I liked the idea of unit tests, but something just rubbed me the wrong way about writing failing unit tests, then writing code to make them pass.
However, after that time I realized some of the value of TDD. I liked the idea of having fewer bugs because I had tests that ran 1000s of times before release. To me that was a big deal. However, for most of my career I have not practiced TDD. I’ve let myself be swayed by the idea that there wasn’t enough time etc. The biggest foray into TDD was the last few months at Infogroup. After I left there, I let the TDD slip by the wayside.
My first project at my new job involved some performance metrics and ranking performance in the metrics. For example, the customer wanted to know how many units you sold in a month, and how you compared to your coworkers in selling. There were a few different levels of employees as well (think regions and districts and all the fun stuff that large multi-national corporations have.)
In a lot of ways this was a perfect candidate to use TDD (are there bad candidates?) It involved a lot of calculations as well as rankings and scores etc. It was more than just a “Pull name from the database and display it on the screen.” Instead, it was more like, for this employee, total up her sales for July, compare it to how many she was supposed to sell, and then see where she falls in the group of sales people. But for a variety of reasons, we didn’t practice TDD.
The project is (nearly) done. As I thought about the project, I wondered what it would be like if we had done TDD. How would the code base look from TDD compared to how it looks without TDD?
This weekend, I had enough of thinking about how it would look and decided to just start writing code, in a clean project. I didn’t completely rewrite the project, and really don’t have a desire to. This was more of a “proof of concept” for me.
The code I have now is actually pretty different from the code I have at work. Some of the changes I’ve noticed are:
- Methods are smaller
- Methods take fewer parameters
- More, smaller, specific classes
All of that was a direct result of TDD. For example, when I wrote a test to calculate the number of sales, I focused only on the essential functions that were required to get that number.
However, the bggest benefit from doing TDD was it forced me to slow down and think about what needed to go where. For example, when I first started writing I had a list of things that a metric should have, some of which were:
- A plan value (how many units should this person have sold?)
- An actual value (how many units did this person sell?)
- A rank (1 to n of all the sales people)
However after I wrote a lot of the code to calculate the values, I made a realization. A metric itself didn’t need a rank, a salesperson needs a rank. If I hadn’t been doing TDD I probably would have created my metric class and then added a property for each value that I thought I needed. But I didn’t do that. Instead I only wrote what I needed to get that test to work. This allowed the code to shift and change over time. I didn’t feel locked into my design, and more importantly, I didn’t feel the need to get my design 100% accurate the first time.
Another, bigger way, that this played out was the type of metrics there are. At this point, I have discovered two metrics, there are metrics that are sums (for example the number of sales) and there are metrics that are averages (for example, average commission.) When I started, I actually just had a single metric. But when I wrote the test for getting the yearly score, I realized that I needed one method to sum for the entire year, and one method to average over the year. But instead of just adding another method, it seemed a perfect time to split that class into a SumMetric and AverageMetric. In the end, or at this point anyway, the two metric classes are very small.
This is in contrast to other projects where at the outset I try to think of all the class types I’ll need (in this case, all the metric types.) And then I’d spend a lot of time trying to figure out where to put a particular method. Is it an abstract base method? Should it be protected or public? And a million other little choices on design.
On this project I put a method on the class where I needed it when the test case came up. But because I had test coverage, I was free to move the method around whereever I needed. In fact, I did this probably a dozen times. I’d put a method on my SalesMetric class and then realize that it wasn’t specific to sales, and then I’d think “Is it a SalesMetric method, a SumMetric method or a base Metric method?” And I’d put it where it needed to go, rerun my tests and verify that everything worked the way it should.
I entered the project thinking TDD would help me in my testing, because if nothing else, I’d know for certain that my metrics are getting calculated correctly. And that’s definitely true. But to me, the biggest advantage of doing this TDD is two-fold. First, I have simpler code. Second, I don’t have to get my initial design 100% perfect, because the code will change, and TDD will allow me to handle those smoothly.