Free Trial

Safari Books Online is a digital library providing on-demand subscription access to thousands of learning resources.

Share this Page URL

12. Applying Test-Driven Development to ... > Refactoring to Support Testing - Pg. 210

210 Chapter 12 Applying Test-Driven Development to an Existing Project A great place to start is by examining the compiler warnings, and the results of Xcode's static code analysis (press Cmd-Shift-B in Xcode, or choose Analyze from the Product menu). Both of these reports describe problems that are already present in the application code, that could potentially cause issues at runtime, and are usually localized and easy to fix. Projects that have been under development for a long time often accu- mulate warnings or analysis issues, even through no fault of the developers: For example, the static analyzer is a fairly recent addition to Xcode so your past self couldn't help you out by using it. Cleaning up these problems will give you immediate feedback, as the number of warnings or reports is reduced.This gives you an important psychological boost as you realize that you're cleaning up your source code and making things better. Refactoring to Support Testing The first problem usually encountered when you start adding unit tests to an existing project--after you've worked up the motivation to write the first test--is difficulty in getting any of the classes to work on its own.With no particular pressure to isolate each class, it's easy to get complex systems of dependencies and coupled behavior between multiple classes so that instantiating any one class in the fixture ends up re-creating almost the entire app. How can you overcome this complexity and separate the app into testable parts--remembering that you cannot yet rely on tests telling you whether any- thing has broken? It may not be necessary when you start testing a class to remove every single connec- tion it depends on; if you can break out a few classes into an independent--but internal- ly coupled--subsystem, you can test that subsystem on its own and worry about cleaning up its innards later. The first step in this process is to identify "inflection points" in the software--places that seem like they could almost be the interface between two separate components. Natural inflection points include the boundaries between the model, view, and controller layers. Others depend on the capabilities and architecture of your own app.These inflec- tion points will not necessarily already represent clean breaks between separate modules; the important part is to identify where they are and what you could do to completely separate the concerns of each module. After you've found an inflection point, you need to make the smallest changes possi- ble to tease the two connected subsystems apart.The changes need to be small because you cannot (yet) test their impact on the app's functionality, so you're potentially intro- ducing risky changes at this point.The risk will prove worthwhile after you have a greater ability to write automatic tests of your app's code, but for the moment it's neces- sary to tread carefully. If there's a point where a test can be introduced for even part of the interaction, take the opportunity to reduce the risk associated with further changes. A change 1 that's often useful at this point is to find a method on one side of the inflection point that uses lots of properties of objects on the other side, and push it across 1. Strictly speaking, it's hard to call these changes "refactoring" because there's no proof that the code's behavior isn't being modified.