Here is a good question, and my reply, from a recent attendee of my Test-Driven Development for Embedded C training.
As I work more with TDD, one of the concepts I am still struggling to grasp is how to test “leaf” components that touch real hardware. For example, I am trying to write a UART driver. How do I test that using TDD? It seems like to develop/write the tests, I will need to write a fake UART driver that doesn’t touch any hardware. Let’s say I do that. Now I have a really nice TDD test suite for UART drivers. However, I still need to write a real UART driver…and I can’t even run the TDD tests I created for it on the hardware. What value am I getting from taking the TDD approach here?
One of the biggest challenges for someone experiencing TDD the first time is working in the small steps. The small steps of TDD appear to be an independent discovery of John Gall’s observation described in the Systems Bible:
I’ve heard people say that software should be like building. You make a plan; buy the materials; and build it. Would remodeling be like new construction? Kind of. You make a plan, find a contractor, agree on the specs, order materials, demo (demolish) the unwanted parts of the building and build it out. Simple step by step.
Does it go that way?
Do business people care about clean code? They behave like they don’t. Well, they do and I can prove it using proof by contradiction. For the best results, read this proof first.
Suppose that a business person does not care about clean code. The business person wants features and they want them fast. So they pressure engineers to just get the code working. This causes the code to deteriorate. The business person wants more features, but the code is a mess so features take longer to add and things that work start to break. If the business person wants features fast, they need clean code and if they need clean code they must care about it too, which contradicts the statement that business people don’t care about clean code. The contradiction proves the original supposition to be false, proving that business people care about code structure, even if they don’t realize it.
(this article is not just for embedded developers)
A few months back I was reading Doug Schmidt’s blog, on The Growing Importance of Sustaining Software for the DoD, and he made the claim
“although software does not wear out, firmware and hardware become obsolete, thereby requiring software modifications”.
It was a bit of a clarifying moment for me. Doug clarified two terms that I suppose were obvious, or maybe not. Software is this thing that can have a long useful life, where firmware is going to be regularly be made obsolete as hardware evolves.
I’d like to add to Doug’s statement:
although software does not wear out, it can be destroyed from within by unmanaged dependencies on firmware and hardware.
Think of all the code denied the potential long life of software due to being infected with dependencies on hardware.
Maybe you read Part 1 of this article. If you did you’ll know it concerns adding tests to legacy code (legacy code is code without tests). You will also know that the code has file scope functions and data that we want to test directly.
My opinion on accessing private parts of well designed code, is that you do not need to. You can test well design code through its public interface. Take it as a sign that the design is deteriorating when you cannot find a way to fully test a module through its public interface.
Part 1 showed how to
#include the code under test in the test file to gain access to the private parts, a pragmatic thing to do when wrestling untested code into a test harness. This article shows another technique that may have an advantage for you over the technique shown in Part 1. Including the code under test in a test case can only be done once in a test build. What if you need access to the hidden parts in two test cases? You can’t. That causes multiple definition errors at link time.
This article shows how to create a test access adapter to overcome that problem.
And a Happy Leap Year Bug
It’s a new year; last year was a leap year; so the quadrennial reports of leap year bugs are coming in. Apologies are in the press from Apple, TomTom, and Microsoft. Trains we stopped from running in China. Somehow calling them glitches seems to make it someone else’s fault, something out of their control. How long have leap years been around? Julius Caesar introduced Leap Years in the Roman empire over 2000 years ago. The Gregorian calendar has been around since 1682. This is not a new idea, or a new bug.
I’m going to try to take one excuse away from the programmers that create these bugs by answering a question that comes up all the time, “How do I test static functions in my C code?”
This is the second article addressing the misconception that TDD ignores design. In the previous article, I explained how TDD acts as a design rot radar. In this article, I’ll explain why I think TDD also acts as a homing beacon for well structured code.
TDD ignores design; that is a frequently stated misconception. Many people get this idea from the code focus of TDD. TDD does not call for the creation of any non-executable design documentation. So the questioning developer gets the idea that there is no design. But I say, “yes there is”.
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.