For the last five years, I have worked for companies that espouse "agile" technologies and development practices. At my last job, the company contracted with ObjectMentor to come in and provide direct training and mentoring as we transitioned our old waterfall process into a shiny new eXtreme Programming (XP) one. We were even so fortunate to have some one-on-one time with Ron Jefferies, Michael Feathers, and Robert Martin to further help up along the path to XP enlightenment. At the time, I was a software architect and largely skeptical of many of the XP practices. Practices like short iterations, collective code ownership, user testing and a planning game were an easy sell to us; the practice of test-first development was not. In fact, in the controversy over whether design and code should be written "test-first" or not, I always sided with folks who felt that architecture should be worked out first, followed by coding it up and then testing. Even though I understood the importance of writing tests to ensure the quality of my code, I still clung to my old waterfall style for actually driving and writing those tests.
Fast forward a number of years to the present day and even though our development practices are very agile, test-first is still not one of those practices. As a champion of unit testing on my team, I happened to come across the book "Test Driven" by Lasse Koskela at book store one day. Thinking that this was just another one of those post-modern XP books (You know the ones that take one of the original XP practices, re-brand it with a new name and then claim to have invented something new?) with a slightly different take on test-first development, I almost didn't give it a second glance. Since I had time to kill, I decided to thumb through it to see if I could confirm my suspicions. After the first few pages I found that I wasn't skimming the book at all; I was actually reading it. My initial skepticism gave way to understanding. When my legs started aching from standing for so long, I retired to a comfortable chair to continue. I was impressed enough that I ended up purchasing the book that same day. So what did I discover and was it enough to change my mind about test-first development? Read on for the answer!
In chapter one of the book, Lasse starts out by dividing the topic of Test Driven Development (TDD) into two different, but related subtopics: Practical TDD and Acceptance TDD. Practical TDD is more or less re-branded test-first development (TFD) from the early days of XP, and the Lasse spends some time going over the advantages of developing software using a TDD approach. Many of these advantages have been hammered on before and to any agilist should consider them standard fare: ensuring high code quality, ensuring that the code isn't over-designed, ensuring that defects are caught early, etc. Into this mix of vintage advantages, he then tosses the concept of Acceptance TDD (ATDD). In contrast to regular TDD (RDDT?) which is primarily aimed at the development of classes and components, ATDD is aimed at the development of systems and subsystems. In short, ATDD is about writing acceptance tests first that the system as a whole must pass in order to be acceptable to the customer, while RTDD is about the development of the individual classes and components that comprise the subsystems. Lasse asserts that being able to do both forms of TDD will ensure high quality code that the customer actually wants. Having been burned countless times at customer acceptance time, ATDD was a topic that interested me immediately. Unfortunately, we have to wait until the later chapters in the book before we get into the details of ATDD. I felt that Lasse makes an excellent case in chapter one for TDD, and his overview of both RTDD and ATDD left me very interested to hear more. The chapter ends on a rough overview of some of the existing tools for doing automated testing and builds and for doing code coverage. Depending on your existing understanding of agile methodologies, you might just consider skimming this chapter.
In chapter two and three, Lasse jumps off into how to begin to do TDD. In order to introduce the reader to the nuts and bolts of how to do TDD, Lasse walks the reader through a contrived development story and the development of a module to fulfill the requirements. Unfortunately, this chapter was not my favorite one but not because it wasn't well-written, or wasn't very informative: quite the contrary is true! My problem with this chapter was that the example was too simple: developing a simple template engine. This has been one of my biggest gripes about TFD for years: all the books and articles that I've ever read about doing TFD just present very simple, almost trivial, examples of how to write code in a test-first manner. In this regard, Lasse goes through the motions of writing the failing test, coding the module, writing the next failing test, etc. until we have a module that passes the tests and meets the requirements. I understand that this is probably the first time that the reader might have seen the entire process laid out end-to-end, but I was disappointed that the complexity of the example wasn't a little more in line with what a developer is likely to see in their daily queue. In chapter three we see how refactoring plays into the process of TDD and Lasse demonstrates how to modify code and tests in the template engine. Chapter three seemed a little too short to me, and seeing as how it was a continuation of the template engine example, most of the information could have been folded into chapter two.
Chapter four steps away from the examples of the previous chapters and focuses on the strategies and patterns for testing. He presents testing patterns, guidelines, and concepts in a very general sense. As a true believer in unit testing already, I found most of this chapter to be rehashing the case and the techniques for unit testing. His section on testing patterns and mock objects was fairly short, but as a first look at the topic, it might be adequate. If you find chapter four to be interesting, you should definitely check out "XUnit Test Patterns: Refactoring Test Code" by Gerard Meszaros, which is considered to be the definitive work on the subject. Lasse does an admiral job of adding his own mini examples, like why one should avoid static singletons in production code, why isolating dependencies make code easier to test, and so on. Again, most of this chapter should be familiar if you are already an agile tester, but it is a very good introduction to the subject.
In the second part of the book, Lasse addresses how unit testing should be applied to specific technologies. In chapter five, the topic is how to apply TDD to web components. This section really raised my interest level because testing web applications has always been a pain for me. Even though this section only weighs in at about 40 pages or so, the information presented is pretty dense. I did feel that the page or two that he spends covering how MVC works was wasteful: if you aren't unit testing web applications then why wouldn't you just skip this chapter? If you are unit testing web applications then you should already have a good notion of how MVC works. Not that this should be considered a serious criticism, it just seemed a little out of place. The rest of chapter contained very useful information to me: how to get started unit testing controllers and views. He also covers testing with the Spring controllers, which is very useful if you have a fortune to be using Spring MVC or Webflow. In the last part of the chapter, Lasse goes over how to create the view in a TFD manner, and he goes over how to use tools like JspTest to do so. He also covers how to do TFD in other web frameworks, like Wicket and Velocity. The section for these last two was very short and I wish that Lasse could have included more examples.
Chapter six deals with testing the database. Lasse makes the compelling case of why it is important to test your database layer and gives some examples of working with straight JDBC to accomplish this task. He quickly moves to higher order abstractions, like DAOs and Hibernate and why these are unit test Nirvana. Since I have the good fortune to be working with Hibernate already, there wasn't anything particularly new or earth-shattering here, but his examples of how to set up and run an in-memory database for testing were solid. Tacked on to the end of the chapter was a page or two about how to test file-system access. Lasse provides some good advice if you are unit testing file accesses, but I found the page or two that he dedicated to the subject insufficient to do the topic serious justice. I would also have liked to see some example code and this is an area that I felt should expanded in the 2nd edition.
Chapter seven deals with unit testing time-based components and threads. Using the same approach of abstraction that he uses elsewhere, Lasse explains how to test logging systems where time-stamp and time formatting are used. He also covers how to test multi-threaded code in the second half of the chapter. I felt that this was a very important chapter since multi-threaded and multi-core is where the industry is headed, and I felt that Lasse's introduction to how to test threaded code was a solid introduction to the subject. Like most things so far, I did feel that it was laking in depth somewhat and I would have been happier with some more examples around the Java 1.5 concurrency API.
Chapter eight addresses how to unit test Swing UI components. If you are doing Swing development, then this chapter is a must-read. After the obligatory discussion about why you should unit test your Swing components, Lasse then gives some very good examples of how to actually do it. From unit testing View components, to working with standard widgets, to drawing custom graphics, there is something here for every Swing developer. If I found his examples in other sections of the book a little sparse, it was definitely not the case in this chapter and I found myself really getting excited about being able to test components that I had previously believed were untestable.
Part three of the book was worth the purchase price of the book for me. As I mentioned earlier, this is the section where he describes Acceptance TDD, the process involved and some of the tools that can make the process easier. In order to cover the subject, the book goes back over some familiar ground: the user story, and shows how ATDD fits into the process of writing the story with testability in mind. The anecdotal stories and methodologies described in this chapter were enough to get me thinking about how I could integrate ATDD into our development process. Chapter twelve of the book addresses exactly this topic and I was able to compile a list of ideas and ways that I could get ATDD to work for me.
So was the book worth it? Absolutely. In addition to reinforcing my already strong beliefs in the power and necessity for unit testing, it also gave me some very good starting points for being able to write the hard tests (web and Swing UI components). In addition, the chapters on ATDD were surely an eye-opener into how productive me and my team could be.