EqualityTestCase

From EggeWiki
Revision as of 23:55, 5 August 2009 by Brianegge (talk | contribs)

<geshi lang="java5">

import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set;

import junit.framework.TestCase;

/**

* This base class is for testing that an object properly overrides
* {@link Object#equals} and {@link Object#hashCode}.
* 

*

* Here are the important contract requirements for the two methods, as * documented in the javadoc documentation for java.lang.Object: *

*

    * *
  1. The hashCode method must return the same integer value * every time it is invoked on the same object during the entire execution of a * Java application or applet. It need not return the same value for different * runs of an application or applet. The Java 2 platform (Java 2) documentation * further allows the hashCode value to change if the information * used in the equals method changes.
    *
    *
  2. *
  3. If two objects are equal according to the equals method, * they must return the same value from hashCode.
    *
    *
  4. * *
  5. The equals method is reflexive, which means that an * object is equal to itself: x.equals(x) should return true.
    *
    *
  6. *
  7. The equals method is symmetric: If * x.equals(y) returns true, then y.equals(x) * should return true also.
    *
    *
  8. * *
  9. The equals method is transitive: If * x.equals(y) returns true and y.equals(z) * returns true, then x.equals(z) should return true.
    *
    *
  10. *
  11. The equals method is consistent. x.equals(y) * should consistently return either true or false. The Java 2 javadoc clarifies * that the result of x.equals(y) can change if the information * used in the equals comparisons change.
    *
    *
  12. * *
  13. Finally, x.equals(null) should return false.
  14. *
* 
* Additionally, if this class implements {@link Comparable},
* {@link Serializable}, or {@link Cloneable}, then the object will be checked
* to see if these methods provide reasonable behavior.
* 
* You must implement two factory methods: getA, getB.
* 
* If the object under test implements Comparable then a < b (< c)
*/

public abstract class EqualityTestCase<T> extends TestCase {

/** * * @return a new instance of an Object which should equate to other objects * created by getA, but be not equals to objects created by getB */ protected abstract T getA() throws Exception;

/** * * @return a new instance of an Object which should equate to other objects * created by getA, but be not equals to objects created by getB */ protected abstract T getB() throws Exception;

/** * @return an object which is not equal to either A or B. This method can * optionally be implemented. */ protected T getC() throws Exception { return null; }

public void testEquals() throws Exception { assertFalse(getA().equals(null)); assertFalse(getB().equals(null)); assertFalse(getA().equals(new Object())); assertFalse(getB().equals(new Object())); //The equals method is symmetric: If x.equals(y) returns true, then y.equals(x) should return true also. assertTrue(getA().equals(getA())); assertTrue(getB().equals(getB())); assertFalse(getA().equals(getB())); assertFalse(getB().equals(getA())); if (getC() != null) { assertFalse(getC().equals(null)); assertFalse(getC().equals(new Object())); assertTrue(getC().equals(getC())); assertFalse(getA().equals(getC())); assertFalse(getC().equals(getA())); assertFalse(getB().equals(getC())); assertFalse(getC().equals(getB())); }

for (Iterator<T> iterator = getObjects().iterator(); iterator.hasNext();) { Object o = iterator.next(); // The equals method is reflexive, which means that an object is equal to itself: x.equals(x) should return true. assertTrue(o.equals(o)); // x.equals(null) should return false. assertFalse(o.equals(null)); } }

public void testHashCode() throws Exception { assertEquals("hashCodes differ for two A objects", getA().hashCode(), getA().hashCode()); assertEquals(getB().hashCode(), getB().hashCode()); assertFalse("hashCode is either not implmented correctly or is very bad\n" + "Both A and B returned a hashCode of " + getA().hashCode(), getA().hashCode() == getB().hashCode()); if (getC() != null) { assertEquals(getC().hashCode(), getC().hashCode()); assertFalse("hashCode is either not implmented correctly or is very bad\n" + "Both A and C returned a hashCode of " + getA().hashCode(), getA().hashCode() == getC() .hashCode()); assertFalse("hashCode is either not implmented correctly or is very bad\n" + "Both B and C returned a hashCode of " + getB().hashCode(), getB().hashCode() == getC() .hashCode()); }

Set<T> set = new HashSet<T>(); set.add(getA());

assertTrue(set.contains(getA())); assertFalse(set.contains(getB())); assertFalse(set.contains(getC())); }

@SuppressWarnings("unchecked") // Comparable<T> is checked only at runtime public void testComparable() throws Exception { if (getA() instanceof Comparable) { assertEquals(0, ((Comparable<T>) getA()).compareTo(getA())); assertEquals(0, ((Comparable<T>) getB()).compareTo(getB())); assertEquals("Item B should compare equally with another item B", 0, ((Comparable<T>) getB()).compareTo(getB()));

assertTrue("Item B should be less than Item A", ((Comparable<T>) getA()).compareTo(getB()) < 0); assertTrue(((Comparable<T>) getB()).compareTo(getA()) > 0); if (getC() != null) { assertEquals(0, ((Comparable<T>) getC()).compareTo(getC())); assertTrue("Item C should be less than Item A", ((Comparable<T>) getA()).compareTo(getC()) < 0); assertTrue("Item C should be less than Item B", ((Comparable<T>) getB()).compareTo(getC()) < 0); assertTrue(((Comparable<T>) getC()).compareTo(getB()) > 0); } } }

public void testSerializable() throws Exception { if (getA() instanceof Serializable) { for (T o : getObjects()) { assertTrue(o.equals(serialize(o))); } } }

private Object serialize(Object value) throws IOException, ClassNotFoundException { ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream(); ObjectOutputStream outputStream = new ObjectOutputStream(outputBuffer); outputStream.writeObject(value); outputStream.flush(); byte[] bytes = outputBuffer.toByteArray(); ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); return objectInputStream.readObject(); }

/** * If you must implement {@link java.lang.Cloneable}, please do it correctly! The clone interface doesn't require * you to make 'clone' a public method, but this what one generally expects for an object which * implements {@link java.lang.Cloneable} * * @throws NoSuchMethodException * @throws SecurityException * @throws InvocationTargetException * @throws IllegalAccessException * @throws IllegalArgumentException */ public void testClone() throws Exception, IllegalAccessException, InvocationTargetException { if (getA() instanceof Cloneable) { for (T o : getObjects()) { Method clone = o.getClass().getMethod("clone", new Class[] {}); Object cloned = clone.invoke(o, new Object[] {}); assertNotSame(o, cloned); assertEquals(o, cloned); assertEquals(o.getClass(), cloned.getClass()); } } }

/** * toString doesn't have to be implemented. There's not a lot of checks we can do to make sure * this is a 'good' toString, other than to make sure the code works and doesn't return null. */ public void testToString() throws Exception { for (Iterator<T> iterator = getObjects().iterator(); iterator.hasNext();) { Object o = iterator.next(); assertNotNull(o.toString()); } }


/** * This will either return a list of one or two objects depending on if getC() is implemented. * @return a list of test objects. */ private List<T> getObjects() throws Exception { List<T> list = new ArrayList<T>(3); list.add(getA()); list.add(getB()); if (getC() != null) { list.add(getC()); } return list; } } </geshi>