Growing object-oriented software, guided by tests was published in 2010 and is already considered a classic in the TDD community. I have found this to be for a good reason. It is the first book that I am aware of that guides you through development of a non-trivial application using TDD as a paradigm.
It begins with a short introduction of testing practices, where topics such as the TDD cycle and “tell, don’t ask” principle are briefly discussed. These are now well known in the testing world and are not covered in-depth; for those who seek more information there are references to explore further. Testing is just a part of the system design process, so there are some guidelines for object-oriented design as well.
The authors, Steve Freeman and Nate Pryce, then quickly dive in developing a working example: a Java desktop application that watches online auctions and bids over XMPP. There is very little left out: they share their design thinking and coding philosophy on every step in the process. It absolutely reflects real life experience. For example, the very first smoke-test seems quite modest in the beginning, yet reveals more unknowns then initially perceived and we are reminded once again how setting up new applications always takes longer than expected.
It’s as if you’re invited to see how senior engineers from Thoughtworks design and implement a system. However, you will not find authors preaching what is perfect code through artifically prepared examples. The problem is real enough – with external dependencies, asynchronous code and UI challenges. Solutions do not seem to be presented in hindsight; we learn and refactor along with the authors. I think this book will especially help those who, after having absorbed yet another talk, book or blog post about software design or testing, still find themselves slightly lost and producing suboptimal code at work.
The writing style is similar to other books in the Addison-Wesley signature series: rigorous, elaborate and clear. Closing chapters such as those on test readability or testing asynchronous code add even more value. Personally I have found myself sharpening my instinct for recognizing where really is a boundary of responsibility for classes. No matter how nice your programming language or framework is, do not hesitate to create new classes. Also, it helps to pay attention to little details such as not passing special strings / hashes but constants / value types. Maintaining your tests (and their setups) brief, informative and effective will take your project a long way.
Update 2013-07-18: Clarified sentence about writing style after feedback from the authors.