Opinions, guest opinions, video… where does AgileSoC.com go next? Book reviews, that’s where! The first book I’ll take a look at is Test-Driven Development for Embedded C written by James Grenning.
When I started looking at agile, I did a lot of reading and a lot of note taking. I’m a binge reader which means once I get going, I read like crazy. Indeed, I found there were so many good ideas in the books I was reading that I found it hard to stop.
Unfortunately, I’m also a procrastinator. So when I did finally stop, I found it very difficult to get going again. But here I am… going again… and the book that got the ball rolling again was Test-Driven Development for Embedded C.
To open, the very first paragraph of chapter 1 reads:
We’ve all done it-written a bunch of code and then toiled to make it work. Build it and then fix it. Testing was something we did after the code was done. It was always an afterthought, but it was the only way we knew.
That’s a perfect way to start since this entire book is about the other way-or right way. James is obviously talking to embedded developers, but that opening applies to a lot larger audience which I think includes an overwhelming majority of hardware developers. In chapter 1, James does a good job of quantifying the benefits of TDD by comparing what he calls the physics of debug later programming (DLP) to the physics of TDD (there’s two good diagrams there that get the point across nicely). Of course, chapter 1 is where we see the TDD micro cycle introduced and motivations that are specific to embedded software developers.
Beyond chapter 1, the book is split into 3 sections: Getting Started, Testing Modules With Collaborators, and Design and Continuous Improvement.
In Getting Started, we get right to the code through an introduction of two unit test harnesses: Unity and CppUTest. James builds some very simple examples to show the mechanics of using both. I like this chapter because it reminds me of how much Bryan and I have borrowed from the software folks to build SVUnit.
Throughout the book, James does a good job of mixing abstract themes and patterns with concrete examples. The first pattern he references (and references several other times through out the book) is the 4-phase test pattern from Gerard Meszaros. Coincidentally, I was refactoring some of my own unit tests at the same time I read this. My tests were a little disorganized so I applied the suggested setup, exercise, verify and cleanup pattern. It worked quite well; one of many good suggestions James has in the book.
The code examples continue in chapter 3 with the first cut at the light driver example. This is an example that grows through to the end of the book. He uses it to walk people through getting used to the TDD cycle using a very basic and autonomous piece of code (chapter 3), using tests to guide development through to a completed application (chapter 4), creating test doubles (chapter 7), spying on production code (chapter 8) and different mechanisms for implementing test doubles (chapter 9). In chapter 10, he digs deeper into the concept of mocking and the use of MockIO as a way to abstract the hardware to do driver development with TDD.
I have a few highlights through chapter 10, which takes readers through Getting Started and Testing Modules With Collaborators.
- From Kent Beck, James borrows the acronym DTSTTCPW (do the simplest things that could possibly work). That’s handy to have in your mind as you think through a minimum implementation required to pass a test.
- Also from Kent Beck: Do you have a test for that? That’s a handy question to ask someone when it looks like implementation is getting ahead of the test suite.
- Solve one problem at a time is a theme that comes up first on page 66 but is emphasized repeatedly; stay focused by solving one problem at a time. In tests, functions, everywhere.
- the idea of a using unit tests as a software vise (via Michael Feathers)
- While the mechanisms for building test doubles in hardware languages would be a little different than in C, the reasons for using test doubles is well explained.
- One line I highlighted on page 130 as again emphasize that it’s tests that drive development:
There is virtually no end to “I’m going to need it soon” thinking. So in TDD, we generally only add what is needed by the current tests.
In the last major section, Design and Continuous Improvement, things start to get a little heavy where James talks about TDD being used within a larger context of responsible development practices. He talks about SOLID design principles from Bob Martin in chapter 11 and also applies them to his evolving light controller example (explaining principles is one thing but showing principles is a definite strength of this book). Chapter 11 is excellent for demonstrating how code structure can and should evolve as scope increases. From page 215:
The problem with much of the legacy code out there today is that as requirements evolved, designs were not improved to more naturally accept the changes.
Chapter 12 has more excellent advice for recognizing and refactoring bad code (this chapter was enough to convince me that I need to finally read Refactoring, Improving the Design of Existing Code by Martin Fowler). Chapter 13 has some very pragmatic advice for adding unit tests to legacy code and using TDD for new features to legacy code.
So from a book written for embedded developers, what are hardware developers supposed to learn?
First, the mechanics and motivation behind TDD are portable. James shows them as being ported to embedded software from the more traditional domain of application development. The same could be done for porting to hardware. You want better code and a better design? Use TDD, regardless of where ever you are.
Next, simple, concise, focused unit tests are sorely lacking in hardware development. This book shows you how to achieve simplicity even though the language and application come from a different domain.
Mocking is another great idea that hardware developers sort of use. But seeing mocking quantified and applied in another domain is eye opening to say the least. Look at something like MockIO and say the idea couldn’t be directly applied to TLM to break dependancies and isolate verification and modelling components (i.e. isolating a UVM sequencer from a UVM driver).
Lastly, all the higher level design considerations that James covers in Design and Continuous Improvement can be directly applied in hardware. Repeat… directly applied. Specifically, I’d bet most hardware developers have never used the word “refactor” never mind understand what it is. That’s crazy. Recognizing and improving brittle code should be a no brainer. As James says on page 252:
Generally speaking, refactoring should be part of everyday development. It’s not on your schedule, and you don’t ask for permission to refactor. You do it to keep the code clean, you do it to help you understand code you’ve never looked at before, you do it to pay for sins of the past.
Along those lines, he has numerous references to Bob Martin and Martin Fowler that can help people write decent code (Clean Code from Bob Martin is the other book I need to finally get to).
Yes, hardware developers can absolutely learn a thing or two from this book! Don’t let the application and language scare you away. Test-Driven Development for Embedded C is written at a level of detail that hardware developers will appreciate. The examples are great and the concepts are absolutely portable. Even better, if you have a chance to sit through James’ TDD for Embedded C tutorial (he’s been offering that at conferences like ESC) and you get a feel for his style, it makes the book that much easier to follow and learn from.
With any practical, hands-on style book, the end of each chapter includes as series of exercises. My favourite from chapter 6 is the first exercise:
1. Think of 100 reasons why TDD could never work for you. Then go review your bug list.
Well? What are you waiting for?