There is a typical development strategy that I believe a lot of developers use to debug and test their embedded software, namely: compile, program, debug, spot test, adjust and then repeat. I used this process myself for years, after all, it seemed natural and made a lot of sense.
Unfortunately, there are a lot of issues with this type of development strategy such as hardware dependence, wasting time programming hardware, too much time spent debugging and spot checks. Regrettably, the latter are often manually intensive “cross your finger” tests. In today’s post, I want to talk a little bit about a self-improvement project that I’ve been working on for several years to improve the way that I write and test my own embedded software.
Back when I was a junior engineer, I came across an interesting and intriguing book that had been released entitled Test-Driven Development (TDD) for Embedded C by James Grenning. The premise of the book was that TDD wasn’t just for software developers working on servers and in web applications but that there was a lot of value that embedded software developers could gain by adopting the principles of TDD. One of the benefits, which I’ve found to be irreplaceable in my own software development activities, was the use of an automated test harness.
Test harnesses are pretty well known and used tools today, but embedded developers still seem to resist their use (depending on their experience and the types of companies they have worked for). They allow a developer to write a series of tests that can then be run against their code to detect defects and verify that the code behaves the way that they expect it to. The thing I love about them the most is that I can make a change to my code and then rerun all the previous tests that I created to discover if I’ve broken anything in my code. If I adhered to the “old practices”, I would most likely spot check the code and have an unfound bug waiting for a client or user to discover.
I was recently working on a project and I wanted to compare how well spot-checking worked running code on the hardware to verify the code worked against building out a series of test cases. I wrote my code, performed the usual spot checks and found that my code was working great. I felt really good about it. I had written it fast and put a lot of sweat into it. In fact, I felt like this very well may have been the best, most robust piece of code I had ever written. The question, though, is would the test harness agree?
So, I started writing test cases that would be integrated into cpputest, a C/C++ test harness, to see what would happen. First of all, in my spot checking on the embedded target, I had created around 20 test cases. When I built out my test harness, I found that I had around 36 test cases. I also found that when I ran my tests that there were no less than five bugs waiting to be discovered in the module. Now these bugs were corner cases and they may never have been encountered in the field, but if they had, they could have resulted in some pretty embarrassing system behavior.
The way that I used the test harness to compare the results to spot-checking isn’t really the right process for someone interested in TDD. In TDD, the developer first writes a test case, then they make it fail and write the code to make it pass. My experiment was simply bolting a test harness and some test cases on to an existing piece of code. The results though in my mind show that there are several important benefits to using automated tests with embedded software that includes:
- Creating tests that continue to check the software integrity even as new features are added
- Creating test cases that are known to actually detect a defect
- Forcing a developer to think about testing and failure modes through-out the entire development cycle
- Improving code robustness
- Allowing a developer to abstract and decouple their application code from the underlying hardware
There are certainly many other advantages, but I often see these areas neglected, and adopting more of a TDD, testing type approach helps generate numerous benefits for not just the companies building products but also the end-users. When I first encountered TDD, I thought that it was too time-consuming and really couldn’t help embedded developers write code faster and more robustly. I don’t have that perspective anymore. In fact, when I start writing any piece of code, the first thing I set up is my test harness because, without it, I feel like I’m just spinning my wheels and developing too slowly.
Jacob Beningo is an embedded software consultant who currently works with clients in more than a dozen countries to dramatically transform their businesses by improving product quality, cost and time to market. He has published more than 200 articles on embedded software development techniques, is a sought-after speaker and technical trainer, and holds three degrees which include a Master of Engineering from the University of Michigan. Feel free to contact him at firstname.lastname@example.org at his website www.beningo.com, and sign-up for his monthly Embedded Bytes Newsletter.