Test Driven: Practical TDD and Acceptance TDD for Java Developers
Buy it now
One Minute Bottom Line
|It is simple to rate this book: everyone should own and read a copy! Yes, it's a Java tome, in a Java series; but it happened that as I was reading it, I was working on a UNIX shell script test harness (which, of course, is software, just like anything else) and thought I'd try to apply the lessons presented. My testing code was finished more quickly than I expected, was better designed than I had hoped, and was more elegant than I thought I could write, all because I applied the principles articulated herein. What's not to like? I can hardly wait to try it on Java code!|
Part 1. A TDD Primer
Chapter 1. The big picture
TDD is presented as a process for writing software ("build it right"). Begin by writing a test that expresses your goal. Write code to satisfy that test. Refactor successful code to improve design without sacrificing functionality. Acceptance TDD borrows much from TDD, but focuses more on the interface between the customer and the development team ("build the right thing"). The tests for acceptance TDD are tests of large-scale features rather than small-scale functionality. Acceptance TDD can create a common language in which the customer can specify desired behavior directly, making them more direct stakeholders in development outcomes.
Chapter 2. Beginning TDD
This chapter begins a discussion of refactoring, the least familiar of the steps involved in TDD. The chapter example, an email templating system, is developed step by step, test by test. This is among the most revealing portrayals of the power of TDD and refactoring I can recall reading.
Chapter 3. Refactoring in small steps
We continue with the email templating example from the last chapter, virtually gutting and redesigning it, but with a control in place to assure us we are moving in the right direction: the tests we have already created and passed. So long as we continue to pass them, the new design still answers our needs.
Chapter 4. Concepts and patterns for TDD
Creating great tests is not an automatic thing. After all, they are software, too! A number of useful observations about how to work with tests are followed by an in-depth discussion of two central concepts in good test-writing: fixtures and test doubles. The former constitute test state, and can be useful in deciding which tests belong together and which should be separated; the latter allows us to mock or stub out functionality that isn't available or is too slow to encourage frequent, rigorous test repetition. Finally, a number of testing design patterns are explicated and discussed.
Part 2. Applying TDD to Specific Technologies
Chapter 5. Test-driving web components
Applying the concepts from Part 1 is relatively simple for systems employing POJOs, but what about systems we really work in? Web components, for instance, may seem daunting candidates for TDD, given that they exist in an environment that is cumbersome to set up and into which it is difficult to peer. For simplicity, this chapter assumes we are using the model-view-controller (MVC) pattern, and begins by discussing the controller, both as a servlet and within a framework like Spring's MVC. Testing involves mocking objects and using dependency injection, but is not in principle much more complex than dealing with POJOs. Views seem as though they would be another matter, but again, mock objects and existing testing frameworks come to the rescue when working with JSP and Velocity. Popular web frameworks? Some (Wicket is the example) come natively with provisions for testing that makes even using TDD within them readily manageable.
Chapter 6. Test-driving data access
Data access is another complex area, particularly as the author subscribes to a definition for unit tests that doesn't allow such tests to talk to the file system or a database! Mock objects come to the rescue, making testing straight JDBC straightforward. Examples using Spring's JdbcTemplate and Hibernate follow, each with solutions that render their challenges tractable. Integration testing is introduced here, using an in-memory database and the DbUnit framework to simplify and speed up the testing cycle. Finally, file access is discussed, and here again, mocking the file system makes for faster tests that are more likely to actually be used.
Chapter 7. Test-driving the unpredictable
Time-based functionality creates special issues in testing, particularly since time never seems to stand still for our convenience! Abstracting the system time can allow us to make time seem to stand still, though, which simplifies things. Multithreaded code is another case where predictions of just what the system will do are often confounded. A discussion of multithreading problems and their solutions occupies the rest of the chapter, including a useful discussion of the new Java 5 concurrency objects.
Chapter 8. Test-driving Swing
How can we possibly automatically test a user interface? It turns out there are things we can test and things we cannot. But there are also ways of programming GUI applications that can make them more conveniently testable and simultaneously drive us towards better design. Moreover, there are tools that can help us test Swing applications: Jemmy and Abbot are presented in the context of an example sufficiently complex to exemplify typical GUI programming challenges.
Part 3. Building Products with Acceptance TDD
Chapter 9. Acceptance TDD explained
Acceptance TDD "is what helps developers build high-quality software that fulfills the business's needs as reliably as TDD helps ensure the software's technical quality." In other words, acceptance TDD helps developers understand the features needed by the customer. Everyone knows when the project is done and can resist adding extra touches the customer doesn't actually need. A key attribute of acceptance tests is that they use the language of the domain and of the customer. User stories can be helpful in identifying testing needs. The cycle is similar to TDD: identify the user story to be tested; write the tests; automate the tests; implement the functionality to pass the tests.
Chapter 10. Creating acceptance tests with Fit
Writing tests in the language of the customer obviously requires something very special in the way of a testing framework. The Fit framework (with its companions FitNesse and FitLibrary) meets this need by allowing customers to specify test inputs and expected outputs in domain language that developers can write tests to execute using the Fit concept of fixtures.
Chapter 11. Strategies for implementing acceptance tests
This is the "tips and tricks" chapter, deepening our understanding of acceptance testing with practical suggestions, approaches, and advice (much of the latter from experience). How is the system connected to the tests? What exactly should we test, and how can we avoid brittle, fragile tests that break every time something changes in the application? How do acceptance tests differ depending upon what kind of application we are testing? These are the kinds of questions explored.
Chapter 12. Adopting TDD
OK, we get it: TDD and acceptance TDD are vital elements in the developer's arsenal. But they are not yet commonly adopted, and anyone looking to use them within a project must inevitably deal with skeptics who will argue that they don't need any such thing. There are arguments to be made and suggestions for how best to foster an environment within which TDD will be become the enabling force it can be. Leading change is hard, but this chapter can help.
Appendices - discuss using the basic tools of Java testing: JUnit, EasyMock, and Ant.
Chapter 13. Test-driving EJB components
(This is an online bonus chapter, not found in the printed text.) For years, the most difficult code to test has been EJB code. The recent EJB 3 specification has improved this situation in many respects, but is not yet used everywhere. Libraries like MockEJB allow us to apply many of the same techniques to EJBs we apply to POJOs, even under the older EJB 2 specification. Session-bean, entity-bean, message-bean, and timer-service testing are covered with many examples.
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)