When you're testing a method, you want the test to only be testing
that method. You don't want things to build up, or you'll be left with a
maintenance nightmare. For example, if you have a database-backed application
then you have a set of unit tests that make sure your database-access layer
works. So you move up a layer and start testing the code that talks to the
access layer. You want to be able to control what the database layer is
producing. You may want to simulate a database failure.
So it's best to write your application in self-contained, loosely coupled
components, and have your tests be able to generate dummy components (see mock
objects below) in order to tests the way each component talks to each other.
This also allows you to write one part of the application and test it
thoroughly, even when other parts that the component you are writing will depend
on don't exist.
Divide your application into components. Represent each component to the rest
of the application as an interface, and limit the extent of that interface as
much as possible. When one component needs to send information to another,
consider implementing it as an EventListener-like publish/subscribe
relationship. You'll find all these things make testing easier and
not-so-coincidentally lead to more maintainable code.
Use mock objects
A mock object is an object that pretends to be a particular type, but is
really just a sink, recording the methods that have been called on it.
A mock object gives you more power when testing isolated components, because
it gives you a clear view of what one component does to another when they
interact. You can clearly see that yes, the component you're testing called "removeUser"
on the user registry component, and passed in an argument of "cmiller", without
ever having to use a real user registry component.
One useful application of mock objects comes when testing session EJBs,
without the hassle of going through the EJB container to do it. Here's a test
class that checks a session EJB correctly rolls back the containing transaction
when something goes wrong. Notice also how I'm passing a factory into the EJB -
this is something that happens quite often when you want to be able to alternate
implementations between test time and deployment.