| JUnit
Testing Tips Fall 2007, David Matuszek |
Scope is important (as always). If you are testing the Transmogrifier class,
and you need a transmogrifier in most of your methods, you would begin by declaring,
but not initializing, a transmogrifier as an instance variable:
class TransmogrifierTest {
Transmogrifier tm;
(It's usually best to give this instance variable a short name, because you
will be using it a lot.) The reason for not giving it an initial value when
you declare it is that this initialization happens only once. You will
typically need a brand new, unspoiled object for each and every test; to make
this happen, initialize your variable in the setUp() method.
public void setUp() {
tm = new Transmogrifier("fiddle",
3);
}
You can use this pattern for any variables you use in multiple tests. Of course, if you only use a variable in one or two tests, you can always declare it and initialize it in just those tests, if that makes the tests more readable.
Your JUnit tests will test whether your methods return the answers you expect. That means you need to know what the correct answers are. When you set up your tests, don't use random numbers, or input from a file, or other things you can't completely control. If you are testing addition, test whether 2 plus 3 equals 5; don't test whether 85674 plus 36627 equals 123301.
The two most important JUnit methods are these:
assertEquals(expectedValue,
methodCallToGetActualValue);
and
assertTrue(condition);
Of these, assertEquals is more useful, because if the assertion
fails, you can find out what value was actually returned. With assertTrue,
you only know that the condition was false.
You can add a message as a first parameter to any assertion method, in order to provide additional information. For example,
assertTrue("x = " + x, 0 <= x && x <=
100);
Typically, methods in a class use other methods in the class. If
method one calls method two, you can't really write
and test method one until
you know method two is working, so you should start with method
two.
In general, look first for the simplest methods, and methods that don't depend
on other methods. Then build up gradually, writing and testing methods that
depend only on methods you already have (and that you know work). Sometimes static methods
are a good place to start, because they are independent of any objects of the
class.
By working "from the bottom up," you can make one method at a time "go green" (pass its JUnit test). You make slow but steady progress toward a complete, working program. While there are no guarantees, this approach makes it much less likely that you will be stuck for hours trying to find an elusive bug.
A constructor returns a new object. Almost always, at least some information about the object is available to "the outside world" (other classes), otherwise there's hardly any point in having the object. You can only test what you have access to.
Sometimes a new object is so "empty" that there's hardly anything meaningful you can ask it until you have used some of the methods of the object. In that case, testing the other methods may be enough.
We try to make methods relatively standalone and independent of one another,
but it doesn't always work out. For example, if you have getter and setter
methods for a variable, it's pretty difficult to test whether the setter works
without using the getter method to see what it did. In fact, you might want
to combine the tests for these into a testGetAndSetValue method.
When testing a method, use any other methods you need. Just don't use methods you don't need.
Private methods can only be tested indirectly, by testing other methods that use the private method.
Some void methods just do output, and have no effect on the future
execution of the program. It is possible to capture the output and make sure
it is correct, but for now we are just ignoring such methods.
Other void methods modify the state of the object in some way.
You can test the state, to see if it has been modified in the way you expected.
Sometimes you can ask the object directly about its state. Other times, you
may need more indirect tests, to see if the object's behavior changes appropriately
in response to the change in its state.
As always, figure out what you expect, and look for that.
If you are testing a six-sided die, try rolling it six hundred times, and make sure each number comes up at least once.
If you are shuffling an array of integers, you want to make sure the result has the same numbers in it. One quick way to do this is to add up the numbers both before and after shuffling; the totals should be the same. You also want to check that the array isn't exactly the same as when you started.
assertEquals doesn't work for arrays. However, the Arrays class
has a static method ,
for one-dimensional arrays. For two-dimensional arrays or greater, use the
method .
Sometimes you want your methods to throw exceptions. For example, suppose
you have a method in the Exam class,
and you want the method to throw an IllegalArgumentException if
the score isn't in the range 0 to 100. Here's how
you would test that:
try {
exam.setScore(-1);
fail ();
catch (IllegalArgumentException e) { }
try {
exam.setScore(101);
fail ();
catch (IllegalArgumentException e) { }
There is often a lot of repetition in tests. For example,
assertEquals("Saturday", d.findDayOfWeek(2000,
1, 1));
assertEquals("Tuesday", d.findDayOfWeek(2000,
2, 1));
assertEquals("Wednesday", d.findDayOfWeek(2000,
3, 1));
This is a case where copying and pasting is probably a good idea--at least, when each line is a new test. However, if you are doing computations in your JUnit tests, you should pay attention to the DRY principle, and do the common work in a method. For example,
ocean.placeCruiserAt(4, 6, true);
assertEquals(count + CRUISER_LENGTH, numberOfSquaresOccupied());
...
ocean.placeDestroyerAt(9, 6, true);
assertEquals(count + DESTROYER_LENGTH, numberOfSquaresOccupied());
In the above example, numberOfSquaresOccupied is a private method used in
several places.
Maybe the test is wrong.
JUnit tests whether a method does what you expected, or returned the result you expected. Usually, this is a result you worked out manually.
Your test method has to be public, its name has to begin with the letters "test", and it has to have an empty parameter list.