The name, Unit Testing, accurately conveys the idea of testing something smaller than a working system. It refers to testing code modules that perform isolatable unique functions. In today’s programming parlance, these are typically called objects. They represent functionality that is specific enough to be named in a flow chart of what the system does and simplistic enough that all the inputs and expected outputs can be readily defined.
This ability to exactly define a code unit’s functionality is key. It enables what is known as ‘white box’ testing which presupposes that the tester has an intimate knowledge of what the code actually does. This knowledge supports the process of writing code to provide its expected inputs under each defined operational situation and verify that the expected output is generated.
Unit tests gate the process of integrating the code module into the overall system. A code block must pass its unit tests completely, without failures, to assure that it will contribute its functionality to the system without adding defects as well. Testing the integrated system should be focused on the interactions between modules, not the operation of the modules themselves.
Unit Testing: Developers Testing Their Own Code
A touchstone of software quality has long been the concept that a programmer should never test his or her own code. The intent behind this is simple. The person who wrote the code is the one most likely to have perceptual blind spots regarding its faults. While this idea is arguably correct, the need for an implicit understanding of the code module’s operation for unit testing overrides it.
As described above, unit testing is white box testing and is predicated on the tester knowing exactly what the code is supposed to do under all operating circumstances. The programmer who wrote it is the obvious choice for the task, blind spots or no. Unit testing requires this deep knowledge of the code and it must be performed at the stage of the production process where the code module is generated. This necessarily makes the code developer our unit tester.
Tips for Successful Unit Testing
Unit testing is where robust system design starts. Having the development engineer writing tests right alongside the code is a prompt for continuous consideration of how the code will be used and what it must do. Developers typically want to create system code rather than tests because that is what they got into the business of programming to do. An astute development manager will actively promote the idea that writing comprehensive unit tests is a basic part of writing good code. A few instances of what to look for in unit tests beyond direct functional verification are in order.
1. Error Trapping
Error trapping is the process of examining the input to a module to detect data that is erroneously formatted or is just simply wrong. It guards against errors generated in other modules and against active challenges to the system’s security. Unit tests should pit the module’s error trapping code against every permutation of bad input the programmer can devise given the inevitable development time constraints. It specifically must verify effective resistance against hacker exploits. Really good unit tests also verify that the module checks its output as well.
2. Graceful Failure
Graceful failure means that, when the module is confronted by input that it can’t either correctly process or disregard, its operation fails with a clear notification to the system of what happened and that it recovers to keep operating on the useful inputs. Testing failure modes goes hand in hand with the error trapping described above. It should be done in coordination with the design of the overall system so that the test verifies that the error notifications are what the system needs to initiate corrective action.
3. Communication Interface Verification
Communication interfaces should be verified during unit testing as well as during integration testing. One of the most overlooked aspects of code modules is how they talk to the rest of the system. Typically, there are system specifications covering data and command communications between code modules that result in boiler-plate code snippets that are inserted into each module for that purpose. The assumption here is that this is sufficient to guarantee proper transfers. In reality, the mating of these routines to the module’s functional code must always be thoroughly verified to see that it performs as expected under all the conditions of the module’s usage.
Just Do It
Most explicitly, unit testing has to be planned into the development process the same way writing code is planned. It needs to be managed and its results continuously reviewed to assure that the system design is a coherent representation of the specifications it was built from. Effective unit testing is one of the best ways to find defects as early and as inexpensively as possible.