Test-Driven Development Insights

Get an overview of test-driven development's role in identifying interface issues, guiding bug fixes, and enhancing code.

Why do we need testing?

One of the many ways these unit tests can help is when debugging application problems. When each unit seems to work in isolation, any remaining problems will often be the result of an improperly used interface between components. When searching for the root cause of a problem, a suite of passing tests acts as a set of signposts, directing the developer into the wilderness of untested features in the borderlands between components.

When a problem is found, the cause is often one of the following:

  • Someone writing a new class failed to understand an interface with an existing class and used it incorrectly. This indicates a need for a new unit test to reflect the right way to use the interface. This new test should cause the new code to fail its expanded test suite. An integration test is also helpful, but not as important as the new unit test focused on interface details.

  • The interface was not spelled out in enough detail, and both parties using the interface need to reach an agreement on how the interface should be used. In this case, both sides of the interface will need additional unit tests to show what the interface should be. Both classes should fail these new unit tests; they can then be fixed. Additionally, an integration test can be used to confirm that the two classes agree.

Bug fixing and code enhancement through test cases

The idea here is to use test cases to drive the development process. A bug or an incident needs to be translated into a test case that fails. Once we have a concrete expression of a problem in the form of a test case, we can create or revise software until all the tests pass. If bugs do occur, we’ll often follow a test-driven plan, as follows:

  1. Write a test (or multiple tests) that duplicates or proves the bug in question is occurring. This test will, of course, fail. In more complex applications, it may be difficult to find the exact steps to recreate a bug in an isolated unit of code; finding this is valuable work, since it requires knowledge of the software, and captures the knowledge as a test scenario.

  2. Then, write the code to make the tests stop failing. If the tests were comprehensive, the bug will be fixed, and we will know we didn’t break something new while attempting to fix something.

Another benefit of test-driven development is the value of the test cases for further enhancement. Once the tests have been written, we can improve our code as much as we like and be confident that our changes didn’t break anything we have been testing for. Furthermore, we know exactly when our refactor is finished: when the tests all pass.

Of course, our tests may not comprehensively test everything we need them to; maintenance or code refactoring can still cause undiagnosed bugs that don’t show up in testing. Automated tests are not foolproof. As E. W. Dijkstra said, “Program testing can be used to show the presence of bugs, but never to show their absence!” We need to have good reasons why our algorithm is correct, as well as test cases to show that it doesn’t have any problems.

Get hands-on with 1200+ tech skills courses.