FloSoft Logo
FloSoft Systems
Developer, not business, friendly

Testing Collections

 
 

Overview

Many novice test writers think that in order to test a collection you need to iterate over it and do a series of assertEqual's. Not only is this tedious, but likely to actually cause misleading results. The reason for the misleading results is that you may not be testing equality the same way java.util.Collection does.

In general it is possible to do assertEquals(collection1,collection2) and if the collections contain the same elements then assertEquals will pass. When dealing with collections in the form of Collection this may cause assertEquals to think that the two collections are not equal when in fact they are.

 
 

Equality in Java

Java tests the sameness of two objects by calling their equals methods; this is done after calling hashCode to see if it is even worth testing for equality. Namely a collection is tested for equality via the following pseudo-code:

	for each element in list1
		if not (list2[elem.index].hashCode()==elem.hashCode() and
		   list2[elem.index].equals(elem))
			return false
	return true
 
 

Understaning hashCode's

By default Object.hashCode() is called when obj.hashCode() is called, unless of course it is overridden, returns the RAM address, in the JVM not the host machine, of the object. This causes problems when you have two instances of otherwise equal objects. Namely unless hashCode is overriden obj1.hashCode()!=obj2.hashCode() is always true. Note most of the java.* classes do this.

 
 

Understanding equals'

The equals method tests if the contents of two objects are the same. Since it was a pre-Java 5 creation it takes an Object as it's argument instead of a parametrized type. This means that there are two steps in detecting equality:

1. Make sure the objects are instances of the same class (or superclass)

2. (optional) Do the field values (or some subset thereof) of both objects equal each other

Object.equals only does this by comparing hashCode values. The following pseudo code shows how Object.equals is implemented:

	if(!o.getClass().equals(this.getClass()))
		return false;

	return o.hashCode()==hashCode();

Actually Object can not do anything more then this without reflection, which requires a too much CPU time to be practicale.

 
 

Implementing Your Own equals()

Since two instances of a class will have different JVM RAM addresses their default hashCode's will differ. Another observation to make here is just because two hashCode's are the same does not mean the instances are the same object. This means unless you need hashCode for some kind of external object ID setting it to 0 is quite acceptable. When it comes to equals() the Collection hierarchy always calls hashCode before calling equals(). Thus overriding hashCode is required. Normally this will suffice:
	public int hashCode()
	{
		return 0;
	}

Assuming that obj1.hashCode()==obj.hashCode(), Collection.equals will then check obj1.equals(obj2) for being true. If it is, then the two items are considered to be equal and depending on the concrete class of Collection will evaluate to being the same element in the same collection. Normally order does matter though, in everything except classes derived from Set, thus the actual loop would be something like this:

Note: thisand other are the two instances we are checking

	// check to see if there are the same class
	...

	// check length
	if(length()!=other.length())
		return false;

	// check equality
	for(int i=0;i < length();i++) {
		T elem1=get(i);
		T elem2=other.get(i);

		if(elem1.hashCode()!=elem2.hashCode() || !elem1.equals(elem2))
			return false;
	}
	
	return true;

Obviously all we need to worry about once we hard code hashCode is equals it self. The prototype for equals in boolean equals(Object o). Since o is an Object and not guernteed to be an instanceof the class we are checking equality on, the first thing we need to is see if o really is the class thus we check that it is an instanceof the class. The second step is to compare fields. So for example we have something like this:

public class Foo
{
	public Foo(int x)
	{
		this.x=x;
	}

	public boolean equals(Object o)
	{
		if(!(o instanceof Foo))
			return true;

		Foo other=(Foo) o;	// safe since we know it is an instance of Foo already

		return x==o.x;
	}

	private int x;
}

 
 

Constructing Hard Coded Collections

While not a strict requirement we should always perform tests against hard coded expected values. Namely if we are testing something like this method:

private Collection doCount(int lim)
{
	Collection out=new ArrayList();

	for(int i=1;i<=lim;i++)
		out.add(i);

	return out;
}

We don't want to replicate the loop in the test class. Among other reasons if we replicate the method's code in the test class then all the values are guaranteed to be equals, but not necessarily what we expect. Namely the test can quite easily pass but in reality the method is broken. So instead we would want to construct the expected list completely independently of the method. There are two techniques I tend to use depending on the nature of the lists and my mood. The first is to have a series of add's like this:


	Collection expected=new ArrayList();

	expected.add(1);
	expected.add(2);
	...
	expected.add(10);

The second is to create a hard coded array that contains the values and iterate through it to create a list version of the array:


	Collection expected=new ArrayList();

	int[] arr={1,2,3,4,5,6,7,8,9,10};

	for(int i:arr)
		expected.add(i);
 
 

Doing the Final Test

Now that we have constructed our list of expected values and knowing what we know about how Collection.equals works all we need to do now is:

	assertEquals(new Foo().doCount(10),expected);

Let's say we for some reason doCount starts at 0 or something like that thisTest will say something like this:

	[0,1,2,3,4,5,6,7,8,9] != [1,2,3,4,5,6,7,8,9,10]		TestFoo.java:13

Namely the assertEquals on line 13 of our test class source code failed because the actual value was [0,1,2,3,4,5,6,7,8,9] and we expected [1,2,3,4,5,6,7,8,9,10].

For reference here is the whole test class:

public class TestFoo extends TestSet
{
	@TestCase
	public void testCount()
	{
		Collection expected=new ArrayList();

		int[] arr={1,2,3,4,5,6,7,8,9,10};

		for(int i:arr)
			expected.add(i);

		assertEquals(new Foo().doCount(10),expected);
	}
}