The project was already two weeks late. The team had been working on it for months and management was pushing for completion. Joe was conducting some last minute testing on the latest beta version when, suddenly, everything came crashing down. The screen flashed an error message:
“Error 3942: Un-expected failure in parser”.
How was this possible? This problem had been fixed weeks ago!
Does that scenario sound familiar? It shouldn’t – recurring bugs in software development are normally a sign of a problem in the development process. It usually happens when additional code is added somewhere to deal with a special case, which introduces a bug in the normal code path. You eventually notice and fix the bug, only to have a similar bug re-appear later when another special case is added.
Unit testing is one method used by development teams to solve this problem. At its core, unit testing is about writing a series of independent small tests to check that individual functions within the code base are doing the right thing.
Unit testing also provides an example of how a function should be used. It is quite common for documentation to become outdated as the code it refers to is updated or modified. A unit test however, will start failing if the code it is testing differs too much from what it did originally, allowing developers to catch and document accordingly.
Refactoring is another situation where unit testing is useful. Sometimes, developers must change how a function is implemented – whether due to an architectural problem or slow code. This often results in large changes in the code base. Unit tests can be used to check that no bugs were introduced when the change occurred.
One of the great things about unit testing is that you can gradually introduce it into your code base. As developers fix existing bugs or add new features they can add new tests. As time goes on, the percentage of the code base covered also grows.
Take, for example, a code base to return a state from a postcode entry. Here are a few unit tests:
- a sample NSW postcode
- a test for a sample ACT postcode (same first number but not the same state)
- an invalid postcode within the 0001 to 9999 range
- an invalid postcode with letters in it.
Whenever the code is modified, you can run unit tests to ensure any existing functionality hasn’t broken. In our example, the postcode function may be modified again to deal with postcodes that don’t match their state prefix (such as postcodes in border areas). The unit tests could be used to check that the existing functionality is still working correctly.
Unit testing isn’t the solution to every problem, however. Although it can be used to solve a class of problems, it will not be appropriate for testing whole programs, or integration into systems. And it is generally unable to detect performance problems such as slow code that nevertheless performs the correct action. It is only a problem if you become over-reliant on this type of testing. The rest of the time, unit testing is a great tool to have in the developer toolbox.
(By Pascal Hakim)