Practicing Test Driven Development

Part of this time of independent study and what I'm trying to focus on at this point in my career is exploring software development as an engineering discipline. I want my software development to be as much of a true engineering discipline as reasonably possible.

In software, it's easy to throw together some code without using any best practices and call it a production ready product. I would imagine that most experienced software developers have seen this at one time or another during their careers.

As an analogy, it reminds me of a civil engineer, when tasked with building a bridge, might show up on the scene with steel girders, metal rope and cement trucks without doing any preliminary design, modeling and stress-testing, and start pounding girders into the ground and pouring the bridge deck and call it complete after a couple of cars drove across without incident. Not the best recipe for longterm success of the bridge.

In software, I've seen best practices followed to varying degrees. In systems in which lives depended upon (e.g. medical devices, automobile braking systems) it's an absolute that best practices are followed to engineer a product that won't fail. However, for any software development, best practices pay off some serious dividends.

I feel like Test Driven Development (TDD) has a solid place in turning one's "software development" into "software engineering". When I first heard about TDD years ago, I thought: "What a strange concept. You write your tests first? Before any functional problem solving code exists?!" However, I've been pleasently surprised when seeing it in action.

A while back I read, and studied, Kent Beck's "Test Driven Development By Example". It's an interesting read. On one hand, doing TDD correctly and effectively seems a bit of skilled craft. On the other hand, the process can be broken down quite simply as repeatedly following these steps:
  1. Add a new test.
  2. Run all tests and see the new test fail.
  3. Make a little change.
  4. Run all tests and see the new test pass.
  5. Refactor to remove duplication.
A couple of the challenges I've faced:
  1. Old Habits Die Hard: I frequently just want to implement functionality before I have a test that covers that functionality. Fortunately, I've found once I get into the flow of writing the tests first, I get a kind of rhythm that keeps me writing the tests first. However, if I fall out of this rhythm it's easy to stop using TDD at that moment.
  2. Non-Straightforward Unit Testing: If you recall my first proof of concept, it's graphical user interface and audio output intensive. This is not as simple and straightforward to write quality unit tests for as some other types of code. To deal with this more effectively in the future, I'm currently exploring mocking frameworks and other options.

Test Driven Development and The Clean Coder

I've currently been reading Bob Martin's "The Clean Coder". It's a good read. It has a good section on TDD (chapter 5) that quickly and concisely sums up the merits of TDD. A quote I found particularly interesting:

"'I can write my tests later', you say. Oh, you can write some tests later. But the tests you write after the fact are defense. The tests you write first are offense. After-the-fact tests are written by someone who is already vested in the code and already knows how the problem was solved. There's just no way those tests can be anywhere near as incisive as tests written first."

In addition, chapter 7 of "The Clean Coder" covers "practicing". I haven't thought much about good old fashioned practicing in recent years. When I read this chapter I wasn't sure that I agreed that practice was necessary. I thought: "My work is my practice". However, as I continue to work on making TDD second nature in my software engineering process, it did occur to me that practicing TDD might be helpful.

So, I did a couple of the things which turned out to be good TDD practice:
  1. I created a simple wave file reader lib. (Download my C++ WaveReader project here).
  2. I created a basic string class. (Download my C++ String Class project here).
These were not libraries I actually needed. There is of course std::string and I'd written a wave file reader/writer years ago that has served me well. However, this was good test driven development practice. Writing these small libraries while strictly adhearing to TDD, I realized a few things:
  • It took a bit longer to write than if I'd not used TDD.
  • I have very high confidence in the integrity of the code. (i.e. It won't fail or fall short).
  • The high confidence in this code will likely pay off in reduced maintenance, support and integrating enhancements.
  • There was no time wasted figuring out how to write UTs for existing functionality after the fact.
  • No worries about what functionality might not have UT coverage.
  • No cutting corners by skipping UTs to deliver early.
  • It might cost a bit more upfront, but the dividends it pays longterm will very likely be worth it.
Date: September 15th, 2016 at 8:17am
Author: Terence Darwen
Tags: Test Driven Development, TDD, Kent Beck, Bob Martin, The Clean Coder, Reading Material, Sabbatical

Previous Next