This example starts with some sample code that reads values from the registry, and has a basic unit test. The code is then refactored to demonstrate the role of unit tests in encouraging code change.
There are only three source units: RegistryTest.dpr, RegistryUnit.pas & RegistryUnitTest.pas. This example only shows the production code (RegistryUnit.pas) from the test code perspective. Sample production code that uses RegistryUnit.pas is not included.
Step 1RegistryUnit has one method (GetRegData) that reads and returns two string values from the registry. The unit test in RegistryUnitTest, sets up the registry with some sample values, calls GetRegData and verifies the output, then cleans up the registry.
The current GetRegData assumes anytime client code calls it, it requires both the InstallPath and ModData to be read from the registry.
Step 2This step assumes that client needs have changed: instead of reading both registry values each time -- now there are instances where only the InstallPath is needed and the other code is wasteful. GetRegData has now been split into two calls, GetRegInstallPath & GetRegModData. GetRegData has been retained so existing client code can remain intact, it now calls the other two methods. A new unit test for GetRegInstallPath passes, but the original unit test now fails.
The problem is the r.OpenKey calls are relative. In the original code, the SampleApp key was opened first:
HKEY_CURRENT_USER Software Sample Co. SampleApp
... then ModuleAData was opened. The key passed was relative, not absolute, so it opened ModuleAData under the current open key:
HKEY_CURRENT_USER Software Sample Co. SampleApp ModuleAData
When the 2 methods were split, the only OpenKey call in GetRegModData attempts to open the following key:
HKEY_CURRENT_USER ModuleAData
... which, of course, doesn't exist.
Step 3A simple fix is made in GetRegModData, and both tests now pass.
Without unit tests in place, a simple refactoring like this can cause headaches. It could have been likely that this refactoring was made by a programmer who required only the InstallPath, and didn't want to mess with the rest of the original method. After the change, the code the programmer required worked fine -- but he'd inadvertantly broken the 2nd method. Without unit tests in place, a bug like this might not have been caught until later portions of the development cycle.
It's instances like this that scare off programmers from touching code that works. XP unit tests help remove the fear of touching working code by posting watches on each important piece of functionality. If anything changes, the programmer receives instant alerts to what's broken.