Virtually every line of code is written in response to some failure. It often is a test failure, but might be a compilation failure, or a web page that does not exist.
Through my 16 years of practicing TDD, I have come to discover that establishing cause and effect is critical to getting every line of code right. I’ve done most my TDD in embedded C and C++.
Over the last couple years, with the help of a couple friends, I built my ruby on rails website. I did not know ruby (or rails obviously). I know it a little bit now. I used to follow the several step procedures to bring up a new feature on my website. I would do the 3–6 steps outlined, and my new page would not come up. I’d spend considerable time trying to find the mistake and eventually get the thing to work. It would have involved several changes. The next day I would likely run into another problem because one of the changes was not really needed and I broke something else. In hindsight, I made changes without establishing cause and effect. My new feature started working often due to offsetting defects.
I started treating each step in the procedure as a test. For example If I tried to bring up a web page, that did not exist, what error would I get? I would look for a way to make one of the changes in the several step procedure and get a different error message. Getting new errors was not, but was it the right error? I learned to get the right error that moved my code in the right direction. I establish cause and effect. Cause and effect?! I am programming as an engineer. What is the problem? Can I impact that problem positively, and move my code in the right direction. Sometimes this cause and effect involves TDD tests, other times it is wiring code that once I get it right, I leave it alone
I just found this definition of engineering “skillful or artful contrivance; maneuvering.” I’d say that is the ideal I strive for in building software and a business. Yes, TDD effects how I run my business. TDD is built on cause and effect. Engineering! I doubt I would appreciate the cause and effect approach to programming (and life) if it were not for TDD.
Have you had a hard time figuring out where to start with Test-Driven Development. What if ZOMBIES could help you build code that does exactly what you think it is supposed to do? What if ZOMBIES at the same time help you to build a test harness that can help you keep your code clean and behaving properly for a long and useful life? What if ZOMBIES could help!
I’m not talking about those zombies! ZOMBIES is an acronym.
Have you written unit tests only later to find they slow you down because your changes cause a lot of test breakage? You think, these tests are not living up to the promises I heard. So you toss the tests and go back to business as usual.
Hey, not so fast. Maybe its not test in general, but your tests or your code. The first project I used TDD on in 1999/2000 we ran into this problem. Some were ready to give up. But I wanted the tests to work and looked for what was wrong. In our excitement we kept copying, pasteing and tweaking the test cases. They were an ugly (in hindsite) mass of duplication. Small changes made for ripple effects through the tests. But I could see, it was our fault.
Someone on Quora asked me “How do you make unit tests less brittle”. Here is a more complete answer based on having written my own bad tests and seen many learners of TDD and unit testing go the wrong direction with their designs and tests.
I spend the day updating the Fake Function Framework, Mike Long’s (@meekrosoft) creation. With my client last week, one of the engineers had some problems with Visual Studio (don’t we all) and the Fake Function Framework (a.k.a FFF). I promised to integrate the changes into the FFF. The FFF is created with a ruby script, but you don’t have to care about that unless you have functions to fake that have more than 10 parameters. But anyway, I spent the day updating and reviewing the ruby script that generates the FFF.
Upper management actually asked me to share my TDD experience as well & so I just published an article internally to our Embedded Software newsletter describing how TDD helped my project. Here’s the summary from that article (I think the dates really say it all):
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:
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?”
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.