After reading Joshua Blosh's Effective Java 2nd edition, I decided to implement equals, hashcode and comparable(where applicable) to every class I have been implementing.
I have written a generic unit test class for equals, hashcode and comparable that will test every contract of each function.
I've attached the generic tester class for anyone who would be interested in seeing them.
My question is, am I overkilling the testing? Have I missed any clause in the equals, hashcode and compareTo contract? These is a tester class I've written once, and used numerous times. However, working on my own, I have not had the chance to get a second opinion with fellow developers.
My class EqualsContractTester
public class EqualsContractTester
{
/**
* according to Object.equals
*for any non-null reference value x, x.equals(x) should return true.
* @param o
*/
public static void testReflexive(Object o)
{
assertEquals("Object o should be reflexibly equal to itself.", o, o);
}
/**
* for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
* @param o1
* @param o2
*/
public static void testSymmetric(Object o1, Object o2)
{
assertEquals("o1 and o2 should be symetrically equal to each other.", o1, o2);
assertEquals("o2 and o1 should be symetrically equal to each other.",o2, o1);
}
/**
* for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
* @param o1
* @param o2
* @param o3
*/
public static void testTransitive(Object o1, Object o2, Object o3)
{
assertEquals("o1 should transitively be equal to o2.", o1, o2);
assertEquals("o2 should transitively be equal to o3.", o2, o3);
assertEquals("o1 should transitively be equal to o3.", o1, o3);
}
/**
* For any non-null reference value x, x.equals(null) should return false.
* @param o1
*/
public static void testNonNullity(Object o1)
{
assertFalse("o1 should not be equals to null!", o1.equals(null));
}
/**
* test that o1 and o2 are different
* @param o1
* @param o2
*/
public static void testDifferent(Object o1, Object o2)
{
assertFalse("o1 should not be equals to o2.", o1.equals(o2));
}
}
My class HashcodeContractTester
public class HashcodeContractTester
{
/**
* Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer
*/
public static void testHashCodeConsistency(Object o1)
{
assertEquals("object hashcode consistency with itself failed! Weird. ", o1.hashCode(), o1.hashCode());
}
/**
* if o1 is equals to o2, then o1.hashcode should be equals to o2.hashcode
* @param o1
* @param o2
*/
public static void testHashCodeEquality(Object o1, Object o2)
{
if (o1.equals(o2))
{
assertEquals("if o1 and o2 are equals, then they should have the same hashcode!", o1.hashCode(), o2.hashCode());
}
}
}
My class ComparableTester
public class ComparableTester
{
/**
* ensure sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) for all x and y.
* (This implies that x.compareTo(y) must throw an exception iff y.compareTo(x) throws an exception.) </li>
* @param <T>
*
* @param o1
* @param o2
*/
public static <T extends Comparable<T>> void assertComparisonReversal(T o1, T o2)
{
assertTrue("Comparison reversal should apply: sgn(o1.compareTo(o2)) == -sgn(o2.compareTo(o1)). ", signum(o1.compareTo( o2)) == -signum(o2.compareTo((T) o1)) );
}
/**
* comparator should be consistent with equals if and only if e1.compareTo(e2) == 0 has the same boolean value as
* e1.equals(e2) for every e1 and e2 of class C
* @param o1
* @param o2
*/
public static <T extends Comparable<T>> void assertConsistencyWithEqual(T o1, T o2)
{
assertEquals("o1 and o2 should be equal. Before testing comparison. ",o1, o2);
assertTrue("since o1 and o2 are equals, o1.compareTo(o2) should return zero!", o1.compareTo(o2) == 0);
}
/**
* e.compareTo(null) should throw a NullPointerException
* @param o1
*/
public static <T extends Comparable<T>> void assertNullPointerException(T o1)
{
try
{
o1.compareTo(null);
fail();
}
catch(NullPointerException e)
{
}
}
/**
* (o3.compareTo(o2)>0 && o2.compareTo(o1)>0) implies o3.compareTo(o1)>0.
*
* @param o1
* @param o2
* @param o3
*/
public static <T extends Comparable<T>> void assertTransitivity(T o1, T o2, T o3)
{
assertTrue("("+o3+".compareTo("+o2+") > 0) && ("+o2+".compareTo("+o1+") > 0 ) && ( "+o3+".compareTo("+o1+") > 0 )", (o3.compareTo(o2) > 0) && (o2.compareTo(o1) > 0 ) && ( o3.compareTo(o1) > 0 ));
}
/**
* ensure that twinO1.compareTo(twinO2)==0 implies that sgn(twinO1.compareTo(differentO3)) == sgn(twinO2.compareTo(differentO3)), for all z.
* @param twinO1
* @param twinO2
* @param differentO3
*/
public static <T extends Comparable<T>> void assertConsistency(T twinO1, T twinO2, T differentO3)
{
assertTrue( (twinO1.compareTo(twinO2)==0) && (signum(twinO1.compareTo(differentO3)) == signum(twinO2.compareTo(differentO3))) );
}
}