Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce
Publisher: Addison-Wesley Professional
One of the challenges of traditional Test-Driven Development (TDD) is to maintain an overall sense of direction while taking baby-steps towards the goal. As I started out with TDD in the early 00's, I noticed something surprising about my software solutions; while I benefitted from the flow and feedback of TDD, I often lacked an overall idea of what the resulting software looked like. I had to build that understanding after the fact in order to reason about future changes and analyze the impact of new requirements. My response was to combine my test driven approach with some sketching and diagrams in order to keep track of the overall design. The goal of my sketches was to identify the main components and, more importantly, the relationships between them. I put-off coding until I had a basic understanding of the solution space. I let my sketches remain a starting-point and allowed myself to deviate from them as I learned more. My lasting impression is that TDD works well in certain contexts but has to be complemented with other ideas. This book presents one such viable TDD approach.
Steve Freeman and Nat Pryce are proponents of the London-style TDD. It's a style characterized by heavy use of mock objects and end-to-end TDD. It's a style that deviates from the original TDD school popularized by Kent Beck. Where Kent Beck's style (aka Chicago-style TDD) is based more around asserting side-effects and results, Freeman and Pryce lend towards interaction-based testing. Personally I prefer the original Chicago-style TDD. My reasons are simply that interaction-based testing tends to couple my tests to implementation details. As I progressed through the book I noticed that Steve and Nat use quite a different object-oriented style too. They make heavy use of interfaces (and I really mean heavy) to express the different roles in the design and they incorporate a functional programming style in the core of their objects. The idea is that the core of each class is written in a functional style with lots of small, side-effect free methods. Typically the functional style stops at the public API of each class. At those boundaries the authors prefer more traditional OO concepts. Given my background with functional programming languages it is an idea that I frequently employ myself. But I've never taken it as far as Steve and Nat do (at least not in an object-oriented language). After reading this book I've found that I've changed my style in the direction of the authors. So far I've been rewarded with significant improvements from a readability perspective.
Growing Object-Oriented Software has received raving reviews. That praise is well deserved. It's a solid work with lot of immediately useful practical content. To me the strongest parts of the book are the initial chapters focused on design in an object-oriented context. All those ideas get put to work in the following chapters where the authors present a worked example of a fictional auction sniper system. That case study is quite code-centric, but augmented with discussions, lessons learned and continuous reflections on the progress and resulting design. A strong point is that the authors admit to specific flawed design decisions and discuss them in detail. This is what happens to us all. It's the very iterative and reflective nature of any design, yet rare to find in programming books. Another strong part of the book is it's inspiration and integration of concepts from Domain-Driven Design. And it doesn't stop with single domain objects. Aggregates and sequences are expressed using a domain vocabulary too. I like that. Unless your customers are programmers, ILinkedList<> and IEnumerable<> are rare to find in the average problem domain.
Finally some reflections on the end-to-end tests presented in this book. Automatic end-to-end tests are terribly hard to get right. Many test suites suffer from long feedback cycles and/or fragile test environments. Such problems limit their use and may actually do more harm than good in the end (I've seen teams spend tremendous energy and precious time on keeping fragile tests sort of running). My own path started some years ago as I applied ideas from TDD at the system level. At approximately the same time Behavior-Driven Development (BDD) was introduced. I took a lot of influence from it. BDD has been characterized as TDD done right. I don't think that characterization holds up. While based on similar work flows, the two techniques serve radically different purposes. Where BDD is about communication within a project, TDD is primarily a design technique. I cannot expect BDD to map out my design. BDD specifications are on another level detached from implementation details. In my unit tests I want those details. Sure, I try to avoid depending on internals but I do want to get behind what's visible to the end-users and test-drive internal components. In Growing Object-Oriented Software, the authors make a case for having both types of tests. Their argument is based on differentiating between external and internal quality. Whereas the former refers to how well the program meets the needs of users, the latter is a fundamental quality that lets us cope with continuous change. Their test-driven approach is targeted at achieving the kind of flexibility and quality necessary for a system to evolve. In that sense, Growing Object-Oriented Software is more of a book on design than test; the tests are used as design and reasoning tools, obviously with the valuable side-effect of providing a comprehensive regression test suite. I'll recommend this book to anyone working with object-oriented technologies.
Reviewed June 2013