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.
TDD is about getting the code to do what the programmer thinks the code is supposed to do. It is about getting the code working as intended, and then keeping it working with the help of a comprehensive suite of automated tests.
Temperature and Pressure
Let’s explore the software and TDD roles in temperature and pressure. Does the software need to react and take special actions when certain temperature and pressure conditions exists? If the answer is no, then don’t look to TDD to help where software is not even involved. If the answer is that the software must react to changes in temperature and pressure, then TDD can play a significant role. How do you verify code that has to respond to a particular T, P, delta-T, delta-P or combination? In the real-world, you need to wait for the conditions to occur. In the lab you may need to put your product into a pressure cooker or add instrumentation. With TDD we can easily drive any T, P scenario we want into the code to see if it decides to do the right thing. We can do this effectively, cheaply and safely.
TDD won’t prove that your product will work when it meets those T and P situations; you may have gotten some of the details wrong. Though if you are any good, you would have gotten far more tests right than wrong. The things that you got right, you have tests to keep them right. As you discover the things you got wrong, you can make them right in both the code, the tests and the product. The tests will help you from adding unintended consequences or side-effect defects to the parts of the code you had working correctly before the change. (Did you know that according to a study by R.B. Grady that 25% of defects in the studied defect lists are from unintended consequences to otherwise successful changes? See Software Process Improvement by R.B. Grady, Prentice Hall, 1997.)
Noisy Faulty Hardware
Now, let’s see about those noisy signals? You told me that this was a hardware problem. What did the hardware guys say?
If my memory serves me, it is not even a design problem. Even in this case, I think I am better off with tests than without tests. The tests will provide you evidence of what the code is doing, and may help you do better rule out the software sooner than otherwise possible. Without good tests, you can’t he sure what your code is doing. With good tests, you can run the tests and see that your code does what the tests say. Maybe we can go look somewhere else for the problem.
I would not say that timing tests are a strong point in favor of TDD, though if you have access to a suitable software readable timer/counter you can write tests that measure code timing. I write about that in my book.
I suspect that when you say timing, you may mean concurrency. You can find a series of articles on my blog about testing code that has concurrency concerns. You can use TDD to your advantage to prevent deadlock. Can you prove your code won’t deadlock? No. But you can write tests to make sure locked resources are locked in the correct order and are released as needed.
To stress your system’s concurrency model you will need more than TDD style unit tests. You will need integration and load tests. With TDD you get a tested design and consequently a testable design. You will have many test points. These test points allow you to write higher level tests where things like load and timing can be exercised. And as you make changes to handle your concurrency problems, you can prevent unwanted side effects by quickly seeing the effect of your changes.
Let me add that in my experience timing and concurrency problems are often the manifestation of some simple logic problem. So even if TDD is not a direct hit for these kinds of problems, TDD does help you keep the desired product behavior as you change the codes concurrency model.
An Early Adopter’s Experience
One of my early TDD for embedded adopters told me, after a using TDD for a full release cycle, about an important benefit to them. They found at the end of the cycle they were not chasing their tails fixing the trivial bugs that they usually sprinkled throughout their code during the year. They wiped out that whole bug family. It meant that their system testing actually found system bugs earlier; these difficult bugs were usually only discovered after release, and often by the customer. Now they discover and fix them before release.
Building Your Tool Box
As a craftsman you need good tools and know how to use them. Software is fragile; simple mistakes can cause great problems, loss of productivity, money, injury and lives. We should do what we can to prevent them. TDD is a tool to help build better software faster. It’s an important tool to add to your tool box. Don’t throw any of your other tools away yet!