Unit testing in embedded systems: 3 myths and an automated tip

Resources

Unit testing in embedded systems: 3 myths and an automated tip

The vast majority of medical devices with electronics contain some sort of embedded processor running firmware. Unit testing is one of a range of modern techniques that can be used to facilitate better reliability of embedded firmware.

Embedded engineers opposed to unit testing will raise a few common objections.  After seeing firsthand how it can improve medical device firmware quality, I decided to write this blog debunking three common myths and sharing my experience using an automatedt tool.

3 Common Myths

  1. Our firmware is very simple, we don’t need unit testing.
  2. We can’t unit test microcontroller code, it’s too close to the hardware.
  3. It will add unnecessary cost burden to our project.

Unit testing is now a widely accepted software engineering practice. However, it is severely under-utilized in the world of embedded firmware because of a few myths. Unit testing involves (usually automated) testing of small software “units” in a much larger program. The benefits are well-known: looser coupling between software modules, simplified regression testing, and greatly improved code quality. Most software engineers working in high-level languages accept that automated unit tests are a critical part of software development.

Our firmware is very simple.

Medical devices firmware is rarely “simple.” It’s true that there are very simple applications for embedded microcontrollers: as timing elements, for example, or as devices which execute very basic logic operations. In such a case, tests are usually unnecessary. That said, truly “simple” firmware, in my experience, is rare. Most embedded firmware actually serves a large range of requirements.

If your firmware is doing anything more complex than sequencing LEDs or reading an analog value to raise a warning indicator, you can almost certainly benefit from having good automated test coverage. Finally, simple firmware often does not stay simple. Scope creep affects firmware just as much as high-level software and new requirements discovered later in the project can dramatically increase code complexity.

We can’t test microcontroller code, it’s too close to the hardware.

I would argue that if your hardware interface code is so tightly coupled to your application logic that they are inseparable, you have failed to ensure loose coupling between modules. At minimum, most hardware register read/writes should be wrapped in some sort of abstraction layer. This allows them to be correctly mocked by a test framework. If performance is a concern, these functions can be marked as inline which removes calling overhead while still keeping the code clean and separate. All that said, there are methods to make frameworks mock register interaction correctly if required.

Unnecessary cost burden

The cost burden is a matter of debate. Industry consensus on the issue is that unit testing has a higher up-front cost yet it results in much fewer defects in the finished software. Shipping with fewer defects is very desirable in embedded firmware. Unlike a web application where you can quickly push to production with a few keystrokes, updating software on embedded processors often requires technician visits, or a trip back to the factory. Recalling an order to re-flash units and fix a critical bug can be very expensive, both in dollar terms and in damage to reputation.

Unit testing also results in software which is much easier to maintain and add features to throughout the software lifecycle. There are situations with “simple” medical device firmware where it is an unnecessary cost burden. However, in most cases unit testing provides a tangible benefit in the form of cleaner software and cost savings as new requirements are discovered and integrated into the existing codebase.

How to Unit Test Effectively: Ceedling

So, now that we know that unit testing is a valuable activity for embedded firmware, how do we go about it? One of the best automated tools I have found to date is called Ceedling, from ThrowTheSwitch.org. It is a free open-source build system designed for embedded projects written in C that integrates the Unity framework, the CMock interface mocking utility, and the CException exception handling library.

If you have good separation between modules (defined as a C header file), Ceedling makes developing and running tests a breeze. CMock integration allows it to scan the header files of all dependent modules and generate appropriate mock functions for your tests, completely decoupling your modules from each other and allowing you to test them in isolation. You can run it either on your host development machine, or in a hardware simulator. It is highly configurable and does not require integration with your IDE. You simply point it at your source files and start writing tests. Its command-line nature makes it easy to integrate with continuous integration systems. If you also use continuous integration systems, you can integrate tests into your build workflow.

Peter Kazakoff is a former StarFish Medical Electrical Engineer. A recent graduate from the University of Victoria. Peter uses unit testing on a variety of medical device projects.

Images: StarFish Medical