Layering has advantages for testing, but only if the mechanism for “connecting” the slices provides flexibility. If a code base has tightly coupled slices via such mechanisms as direct concrete class dependencies and static methods, it is difficult to isolate a unit for testing, despite the layering. This makes most automated tests into integration tests, which can be complicated and can run slowly. In many cases, testing can only be accomplished by running the entire system.
Contrast this with a code base where the layers are separated by interfaces. Each slice depends only upon interfaces defined in the slice beneath it rather than on specific classes. Dependencies on such interfaces are easy to satisfy with test doubles at test time: mocks, stubs, and so on. Unit testing is thus simplified because each unit can truly be isolated. For example, the UI can be tested against mock business layer objects, and the business layer can be tested against mock data access objects, avoiding live database access.
The layered approach has allowed Lisa’s team to succeed in automating tests at all levels and drive development with both technology-facing and business-facing tests.
Another example of an approach to testable design is Alistair Cockburn’s Ports and Adapters pattern [Cockburn, 2005]. This pattern’s intent is to “create your application to work without either a UI or a database so you can run automated regression tests against the application, work when the database becomes unavailable, and link applications together without any user involvement.” Ports accept outside events, and a technology-specific adapter converts it into a message that can be understood by the application. In turn, the application sends output via a port to an adapter, which creates the signals needed by the receiving human or automated users. Applications designed using this pattern can be driven by automated test scripts as easily as by actual users.
See the bibliography for more information on Alastair Cockburn’s Ports and Adapters pattern.
It’s more obvious how to code test-first on a greenfield project. Legacy systems, which aren’t covered by automated unit tests, present a huge challenge. It’s hard to write unit tests for code that isn’t designed for testability, and it’s hard to change code that isn’t safeguarded with unit tests. Many teams have followed the “legacy code rescue” techniques explained by Michael Feathers in
The bibliography has links to more articles about “rescue” and “strangler” approaches to legacy code.
Agile testing in a legacy mainframe type of environment presents particular challenges, not the least of which is the lack of availability of publications and information about how to do it successfully. COBOL, mainframes, and their ilk are still widely used. Let agile principles and values guide your team as you look for ways to enable automated testing in your application. You might have to adapt some techniques; for example, maybe you can’t write code test-first, but you can test soon after writing the code. When it’s the team’s problem to solve, and not just the testers’ problem, you’ll find a way to write tests.
Testing Legacy Systems
John Voris, a developer with Crown Cork and Seal, works in the RPG language, a cousin of COBOL, which runs on the operating system previously known as AS 400 and now known as System i. John was tasked with merging new code with a vendor code base. He applied tenets of Agile, Lean, and IBM-recommended coding practices to come up with an approach he calls “ADEPT” for “AS400 Displays for External Prototyping and Testing.” While he isn’t coding test-first, he’s testing “Minutes Afterward.” Here’s how he summed up his approach:
• Write small, single-purpose modules (not monolithic programs), and refactor existing programs into modules. Use a Presenter First development approach (similar to the Model View Presenter or Model View Controller pattern).
For more information about Presenter First development, see the bibliography.
• Define parameter interfaces for the testing harness based on screen formats and screen fields. The only drawback here is numbers are defined as zoned decimals rather than packed hexadecimal, but this is offset by the gain in productivity.