ibooksonline

We Don’t Have Time

We all need more time. Where will we find time to write all this test code? There is barely enough time to write the production code we need. There are more lines of test code than production code for the LedDriver. But does that really matter?

If people programmed error-free and at a constant rate, then there is some reason for concern. But people do neither. The time-consuming parts of programming are thinking, problem solving, and confirming solutions. Confirming solutions can be done many ways, Debug Later Programming (DLP) or TDD to name two. The important question is, did writing the test impede or speed your progress?

Many proficient practitioners proclaim that TDD makes them go faster. They report a productive and sustainable pace. The speedup comes from reducing current and future debug times, and from having a cleaner code base with tests as executable documentation.

What if TDD takes a little more time? There are other costs besides development time: customer dissatisfaction, lost sales, warranty repair, defect management, field service...the list goes on. Maybe it is worth it to you and your customers to spend more time and deliver fewer problems to the field. Also, you may become one of those people who goes faster with TDD.

If you look only at the time it takes to get the production code written, you are not looking at the whole job. You still have to get the bugs out. How much time do you currently spend testing and debugging code? The most popular answer I have heard, from polling conference attendees, is 50 percent. That is a lot of time. The first place to look for the time to do TDD is in your current practice. You should be able to trade some reactive debug time for the proactive TDD approach. In the next few sections, we’ll look at some common unit test approaches that could be at least partially replaced by TDD.

Manual Test

If you are manually unit testing, use some of that time. If you are in a legacy code environment, you won’t leave manual testing completely behind, but you could start to develop new code using TDD or write tests for some of the untested legacy code.

The initial investment in manual test may be lower than automating tests, but it is not sustainable; it has a nearly zero future return. A change to manually tested code nullifies the prior manual tests. You have to run the tests again. Because they are manual, we tend to rationalize running only a subset of the tests. When you don’t rerun the right tests, you get the joy and cost of a future bug.

Custom Test Harness

From time to time, we have all written a test main and a few test stubs that exercises newly written code. The test main exercises the code under test, and stubs provide indirect inputs and log their parameters so we can inspect the behavior. You have created a custom test harness.

These tests are very helpful; they improve the quality of the code so that we are integrating better working code into the product. But too often, after integration, the tests fall into disrepair, as all testing moves to the integrated system. The tests fall out of sync with the production code, and the return on investment is diminished. Your custom test harness was helpful for a while.

Often custom test harnesses have a poor return on investment. They often become incompatible and are discarded after very few uses. The custom-crafted test main also takes more effort than writing tests that plug into a test harness like CppUTest or Unity.

Unit Test by Single-Step

Another manual unit test approach is to single-step through the code under test with a debugger. This is a slow and inherently nonrepeatable process. When change comes, as it always does, the single-step unit test has to be repeated. Because it’s a long and tedious process, it is likely to be a less thorough job the second, third, and Nth times through. We’re only human; we make mistakes and miss subtle interactions within the code.

The shelf life of these tests is even worse than the shelf life of the test main approach. Any single change invalidates prior tests. You have to start over and do it again. So, the manual test effort will tend to grow over time. But you can’t afford that time, so you don’t rerun all the needed single-step tests, and what happens? A bug creeps in, costing future effort too.

Documented and Reviewed Unit Test Process

I consulted at a company that had a very well-defined process. Well-defined and big are usually synonymous when it comes to processes. Their process manual was big. Their process police force was big, and they had a big stick.

They were assessed at CMM level three. They had good conformance and enforcement. One area covered by their process was unit testing. The process consisted of first documenting the unit test procedure and then getting the procedure reviewed and approved. Then they had to record evidence of executing the process. I asked the engineer, Dave, how they used this procedure. Here is how that conversation went:

James:

How do you do unit testing?

Dave:

We have a unit testing standard. We write a unit test plan for each function.

James:

Does the unit test plan get reviewed?

Dave:

Yeah, we do a formal technical review on the plan.

James:

When do you perform the unit test plan?

Dave:

We run it before the code has been through its formal technical review.

James:

So if there are holes in the test plan, the reviewer can make suggestions and improvements to fill those holes.

Dave:

Yes, that’s how it goes.

James:

What does the unit test plan look like?

Dave:

We follow the standard template and add the plan as comments before every function in the source code. The plan becomes part of the code. It includes a series of operations performed on the code, checking various conditions. We make sure we check each branch.

James:

What is it like to run the tests?

Dave:

We use the debugger or the emulator and single-step through each statement and verify it does the right thing. We’re really thorough.

James:

It sounds like it. It takes a lot of time I bet.

Dave:

Sure does.

James:

What happens the next time you change that function?

Confidently Dave says:

We do part of the tests over, based on what we changed.

James, knowing the answer to the question:

Does code like this change very often?

Dave says accusingly:

Yes, the systems engineers never can make up their mind.

James:

What happens as there are more changes?

Dave:

We rerun those parts of the unit test affected by the change.

James:

How do you know what part of the plan needs to be rerun?

Dave:

It’s a judgment call.

This big process took a lot of effort. It made everybody feel good because of all the investment in the software quality. Unfortunately, this kind of effort too often returns little on the investment. When the manual unit test process is repeated, the process gets boring, shortcuts naturally follow, and bugs find their way into the code.

I suggest test automation over test documentation. Test automation is the gift that keeps on giving. If you’re using a process that is similar to Dave’s, it’s time to stop! Spend your unit test dollars somewhere else. By the way, the tests are documentation too. Dave’s company had product safety requirements; we settled on reviewing the test cases to help assure the cases were thorough.

Where Do Your Unit Test Dollars Go?

When you look at how to pay for the unit tests that are written as part of TDD, take an honest look at your current process. Maybe your process is ad hoc or you write a test main or you document a unit test procedure or single-step through the code. Those activities cost a lot to implement but have a very limited payback.

You are already paying for unit tests either directly as mentioned or indirectly through long debug cycles. Consider spending some of your unit test effort on TDD rather than your current process. With TDD the tests are run with every change, the tests evolve along with the code, and the investment is returned many times over.