A recent TDD for Embedded C attendee asks me “TDD does not help reduce the time I spend in the lab during system integration testing”
(This is asked in the context of embedded development. But the answer is half applicable to any development where there is integration.)
TDD should definitely help reduce the time you spend in integration. How does it do that? It helps you eliminate the non-integration problems before you get to the integration lab. An honest appraisal of what you do in integration may reveal that during integration you are finding problems you could have discovered before integration. It would help reduced integration time if you find fewer problems during integration. Don’t you think?
This cause/effect diagram helps to visualize the relationship of TDD to time spend in integration.
Another attendee of my training tells me “The quality of my application is exceptional, adding TDD is an overhead that brings no added value.”
You statement makes me ask a few questions. How did you get the exceptional quality? Is your application finished? Will it be changed? How do you plan on maintaining exceptional quality?
One of the attendees of my training objected to TDD stating “TDD does not resolve the real-world (temperature, pressure, timing, noisy signals, etc.) issues that my project is encountering.”
You are right! I’ll add TDD does not resolve anything. TDD is not a magic incantation that solves any problem the embedded developer may encounter. From discussions at your company, I think you realize this. But it does not change the fact that you have to spend a lot of time chasing these kinds of problems. So let’s see how TDD can support this activity.
I’m going to bend the example a little, and look at how to write tests that interact with an interrupt service routine and again with a semaphore. I’ll also get some help with the tedious parts of building fakes by using Mike Long’s Fake Function Framework. (BTW: I am using a beta version, that has changes the released version does not yet have.)
This figure shows the concurrent runtime relationship between the
MessageProcessor and its
In the last article, the
OSSemPend() test-double was coded to handle a specific
OSSemPend() application and test need. The semaphore was being used to signal when there is a message to process. It was the first need for a
OSSemPend() test double and was quickly developed. As more RTOS dependent code is brought under test, a more general solution will be needed.
In this article, we’ll look at a test double that can be customized for each application.
When you’ve got legacy code that depends on the Real-time Operating System, you have a challenge to get your code off the target for unit testing. If you want to test with the concurrency provided by the RTOS, these are not really unit tests, and you won’t be able to write thorough tests, and you need unit tests to be through.
You’re going to need to make a test-double so you can get your code off the RTOS and into your development system for unit testing. In this article we’ll go through the steps to get started.
I’m in agreement with Robert Glass when he says “100% test coverage is insufficient. 35% of the faults are missing logic paths.” It’s not controversial, but I’d like to give my perspective on it.
If you have an automated unit test suite, low code coverage is an indication that you need more tests. Unfortunately, high code coverage does not tell you if you have enough tests or the right tests. Adding to Robert Glass’ observation, executed code is not necessarily tested code. Imagine a test case that runs through many lines of code, but never checks that they are doing the right thing. At best this is the “I don’t have any bad pointers” test.
Some silicon vendors extend the C language so the programmers can easily interact with the silicon. Using these extensions tie production code to the silicon vendors compiler and consequently the code can only run on the target system. This is not a problem during production, but is a problem for off-target unit testing.
The good news is that we may be able to get around this problem without having to change production code, one of our goals when adding tests to legacy code.
It’s day one of adding tests to your legacy C code. You get stopped dead when the compiler announces that the code you are coaxing into the test harness can’t be compiled on this machine. You are stuck on the Make it compile step of Crash to Pass.
Moving your embedded legacy C code (embedded C code without tests) into a test harness can be a challenge. The legacy C code is likely to be tightly bound to the target processor. This might not be a problem for production, but for off-target unit testing, it is a big problem.
For C we have a limited mechanisms for breaking dependencies. In my book, I describe at length link-time and function pointer substitutions, but only touch on preprocessor stubbing.
In this article we’ll look at
#include Test-Double as a way to break dependencies on a problem
Creating automated tests can be very difficult, especially when the code has gotten long in the tooth and was not created with automated tests to begin with. Many product development teams don’t invest in automated tests. They think they cannot afford them. They think their product is different and can’t be tested automatically. This thinking is flawed.
Back in the products younger days, manual test was not too time consuming. But slowly that changed. The system grows, the manual test effort grows. Eventually, it seems that no amount of manual test effort finds all the problems.
In this article I show a simple model that illustrates why manual test is unsustainable and that a sustainable software product development effort must include considerable test automation.