/******************************************************************************* | |
* Copyright (c) 2009 by SAP AG, Walldorf. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/epl-v10.html | |
* | |
* Contributors: | |
* SAP AG - initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.jst.ws.jaxws.testutils.jmock.testcases; | |
import java.lang.reflect.Modifier; | |
import java.util.Iterator; | |
import junit.framework.TestCase; | |
/** | |
* An abstract test case for the <c>Object.equals(Object)</c> and <c>Object.hashCode()</c> methods. This test case performs the tests for <c>equals</c> | |
* and <c>hashCode</c> simultaniesly as they are tightly connected. The tescase test the following properties of equals/hasCode: | |
* <p> | |
* 1. equals reflexivity 2. equals symetricity 3. equals transitivyte 4. hashCode statbility 5. hashCode immutability 6. equals is suitable for | |
* subclasses | |
* </p> | |
* | |
* The test cases is abstract and need to be sublcassed by concrete test case. Several abstract methods need to be implemented by concrete test cases. | |
* | |
* @author Hristo Sabev | |
* | |
* @param <T> | |
*/ | |
public abstract class EqualsTestCase<T> extends TestCase | |
{ | |
private final Class<T> testClass; | |
/** | |
* Creates a new <c>EqualsTestCase</c> instance to test the equals method of the supplied class | |
* | |
* @param testClass - | |
* the class whose equals method will be tested | |
*/ | |
public EqualsTestCase(Class<T> testClass) | |
{ | |
this(null, testClass); | |
} | |
/** | |
* Creates a new <c>EqualsTestCase</c> instance to test the equals method of the supplied class. This test case also takes a name paramter, which | |
* i passed to <c>TestCase</c> super constructor. I.e. this is the name of the test case | |
* | |
* @param name - | |
* name of the test case | |
* @param testClass - | |
* the class whose equals method will be tested | |
*/ | |
public EqualsTestCase(String name, Class<T> testClass) | |
{ | |
assertNotNull("test class passed in constructor was null", testClass); | |
this.testClass = testClass; | |
} | |
/** | |
* Obtains a new iterator of instances for the tested class. | |
* | |
* @param iterations - | |
* number of iterations i.e the number of times one can invoke <c>Iterator.next()</c> withouth getting <c>NoSuchElementException</c> | |
* exception | |
* @return a new iterator of instanced for the tested class; | |
*/ | |
abstract public Iterator<T> newObjectIterator(int iterations); | |
/** | |
* Obtains a new instance of the tested class. Each call to this method should return a new instance, which is considered equal to the instance | |
* returned by the prefvious call but is not identical. For example if the tested class was <c>String</c> the most obvious implementation of this | |
* method would be <c> | |
* <p> | |
* return new String("test string") | |
* </p> | |
* </c> | |
* | |
* @return a new instance of the test class assumed to be equal to the previously returned instances | |
*/ | |
abstract public T newEqualInstance(); | |
/** | |
* Obtains a new instance of a single class, which is a subclass of the tested class. The subclass must not override the <c>equals</c> method of | |
* the tested class. All instances should be equal. The returned instances should have the same values of all the inherited fields as the | |
* instances retured by <c>newEqualInstance</c> | |
* | |
* @return returns a new instance of single sublcass of the tested class. Each call should return an instance equal to the previously returned but | |
* not identical. Null if the tested class is final | |
*/ | |
abstract public T newAncestorEqualInstance(); | |
/** | |
* Obtains a new instance of the the tested class. The single restriction on the returned instances is that they should not be equal to the | |
* instances returned by <c>newEqualInstance</c> | |
* | |
* @return a new instance of the test class. The newly returned instance is not equal to the instances returned by <c>newEqualInstance</c> | |
*/ | |
abstract public T newNonEqualInstance(); | |
/** | |
* Modifies a given instance of the tested class. The instance should be modified in such a way that it's not equal to the instances retured by | |
* <c>newEqualInstance</c> ans <c>newNoneEqualInstance()</c>. If the tested class is immutable then the implementation of this method should be | |
* empty | |
* | |
* @param instance - | |
* the instance to be modified. | |
*/ | |
abstract public void modifyObjectInstance(T instance); | |
/** | |
* Obtains the number of comparisons that will be made for the reflexivity test. This method can be overriden by subclasses to specify a different | |
* value. By default this number is 100. | |
* | |
* @return - the number of comparisons that will be made for the reflexivity test. | |
*/ | |
public int getIterationsForReflixivityTest() | |
{ | |
return 100; | |
} | |
/** | |
* Obatains the number of comparisons that will be made for the symitricity test. This method can be override by subclasses to specify a different | |
* value. By default this number is 1000 | |
* | |
* @return - the number of comparisons that will be made for the reflixivity test. | |
*/ | |
public int getIterationsForSymetricityTest() | |
{ | |
return 1000; | |
} | |
/** | |
* Obatains the number of comparisons that will be made for the transitivity test. This method can be override by subclasses to specify a | |
* different value. By default this number is 2000 | |
* | |
* @return - the number of comparisons that will be made for the transitivity test. | |
*/ | |
public int getIterationsForTransitivityTest() | |
{ | |
return 2000; | |
} | |
/** | |
* Tests that equals is reflexive. It enforces the rule a.equals(a) == true. This test also tests that hashCode function is stable(). I.e. | |
* hashCode doesn't change it's value between several invocations, as well as that hashCode is immutable over object change. The test strategy is | |
* to create an new object iterator and then for each returned instance: | |
* | |
* <ol> | |
* <li>Compare it with itself</li> | |
* <li>check that its hash code stays the same</li> | |
* <li>modify the returned instance by calling <c>modifyInstance</c> and then check the hashCode again</li> | |
* </ol> | |
* | |
*/ | |
public void testEqulsReflexive() | |
{ | |
final int iterationsForThisTest = getIterationsForReflixivityTest(); | |
final Iterator<T> objectTiterator = newObjectIterator(iterationsForThisTest); | |
while (objectTiterator.hasNext()) | |
{ | |
final T objectT = objectTiterator.next(); | |
assertTrue("equals is not reflexive", objectT.equals(objectT)); | |
final int initialHashCode = objectT.hashCode(); | |
for (int i = 0; i < iterationsForThisTest; i++) | |
{ | |
assertEquals("hashCode is not stable upon multiple calls", initialHashCode, objectT.hashCode()); | |
} | |
modifyObjectInstance(objectT); | |
assertEquals("hashCode is not stable upon modification of the object", initialHashCode, objectT.hashCode()); | |
} | |
} | |
/** | |
* Tests that equals is symetric i.e. a.equals(b) <==> b.equals(a). This method also tests that hashCode behaves correctly in sense a.quals(b) => | |
* a.hashCode() == b.hashCode(). This test uses two object iterators. One for a and one for b, where a and b are from the above rule. Each of | |
* these iterators are instantiated with the same number of elements. This method also checks that whenever two instances returned by the two | |
* iterators are equal then their hascode is equal. | |
* | |
*/ | |
public void testEqualsSymetric() | |
{ | |
final int iterationsForThisTest = getIterationsForSymetricityTest(); | |
final Iterator<T> objectTIterator1 = newObjectIterator(iterationsForThisTest); | |
final Iterator<T> objectTIterator2 = newObjectIterator(iterationsForThisTest); | |
while (objectTIterator1.hasNext()) | |
{ | |
assertTrue("The two object iterators don't have equal number of objects", objectTIterator2.hasNext()); | |
final T objectT1 = objectTIterator1.next(); | |
final T objectT2 = objectTIterator2.next(); | |
if (objectT1.equals(objectT2)) | |
{ | |
assertTrue("Equals is not symetric", objectT2.equals(objectT1)); | |
assertTrue("Hashcode is not the same for equal instances", objectT1.hashCode() == objectT2.hashCode()); | |
} else if (objectT2.equals(objectT1)) | |
{ | |
fail("Equals is not symetric"); | |
} | |
} | |
} | |
/** | |
* Tests that equals is transitive i.e. a.equals(b) & b.equals(c) => a.equals(c). This test uses three object iterators. One for a, one for b, and | |
* one for c, where a,b,c are from the above rule. Each of these iterators are instantiated with the same number of elements. | |
*/ | |
public void testEqualsTransitive() | |
{ | |
final int iterationsForThisTest = getIterationsForTransitivityTest(); | |
final Iterator<T> objectTIterator1 = newObjectIterator(iterationsForThisTest); | |
final Iterator<T> objectTIterator2 = newObjectIterator(iterationsForThisTest); | |
final Iterator<T> objectTIterator3 = newObjectIterator(iterationsForThisTest); | |
while (objectTIterator1.hasNext()) | |
{ | |
assertTrue("The three object iterators don't have equal number of objects", objectTIterator2.hasNext()); | |
assertTrue("The three object iterators don't have equal number of objects", objectTIterator3.hasNext()); | |
final T objectT1 = objectTIterator1.next(); | |
final T objectT2 = objectTIterator2.next(); | |
final T objectT3 = objectTIterator3.next(); | |
if (objectT1.equals(objectT2) && objectT1.equals(objectT3)) | |
{ | |
assertTrue("equals is not transitive", objectT2.equals(objectT3)); | |
} | |
} | |
} | |
/** | |
* Tests that <c>equal</c> returns false if <c>null</c> is passed as argument | |
* | |
*/ | |
public void testEqualsReturnsFalseIfNullPassed() | |
{ | |
final T objectT = this.newEqualInstanceCheckCorrect(); | |
assertFalse("equals returned true when null passed for other object", objectT.equals(null)); | |
} | |
/** | |
* Tests that <c>equal</c> returns false if a non-equal object is passed as an argument. The two non equal instances are created by calling | |
* <c>newEqualInstance</c> and <c> newNonEqualInstance</c> | |
* | |
*/ | |
public void testEqualsReturnsFalseOnNonEquals() | |
{ | |
final T objectT1 = this.newEqualInstanceCheckCorrect(); | |
final T objectT2 = this.newNonEqualInstanceCheckCorrect(); | |
assertTrue("Equals reported true for instances assumed non equal.", !objectT1.equals(objectT2) && !objectT2.equals(objectT1)); | |
} | |
/** | |
* Tests that <c>equal</cL returns true if an equal object is passed as an argument. The instances that are compared are obtained by two | |
* consecutive calls to <c>newEqualInstance</c> | |
* | |
*/ | |
public void testEqualsReturnsTrueOnEquals() | |
{ | |
final T objectT1 = this.newEqualInstanceCheckCorrect(); | |
final T objectT2 = this.newEqualInstanceCheckCorrect(); | |
assertTrue("Equals reported false for instances assumed equal.", objectT1.equals(objectT2) && objectT2.equals(objectT1)); | |
} | |
/** | |
* Tests that equals returns false if an object of another class is passed. In case that the tested class is not final this test passes a subclass | |
* of the test class as an argument to <c>equals</c>. The passed ancestor object has the same value for all its field. This is intended to cahtch | |
* wrong implementations using "instanceof" rather than <c>getClass() == other.getClass()</c> | |
* | |
*/ | |
public void testEqualsReturnsFalseIfOtherClassPassed() | |
{ | |
final T objectT = newEqualInstanceCheckCorrect(); | |
Object object = newAncestorEqualInstanceCheckCorrect(); | |
if (object == null) | |
{ | |
// class is final. No ancestor could be created. Use some other class. | |
object = new Object(); | |
} | |
assertFalse("equals returned true when object of another type passed", objectT.equals(object)); | |
} | |
/** | |
* Tests that <c>equals</c> is properly implemented for use in subclasses. It takes two equal ancestor instances and compares them for equality. | |
* Since the ancestor class does not override the equals method. The tests asserts that equals should return true. | |
* | |
*/ | |
public void testEqualsSuitableForAncestors() | |
{ | |
final T ancestorT1 = newAncestorEqualInstanceCheckCorrect(); | |
final T ancestorT2 = newAncestorEqualInstanceCheckCorrect(); | |
if (ancestorT1 == null) | |
{ | |
assertNull( | |
"The newAncestorInstance() method returned null on the second invocation, while it return a value differet than null from the first", | |
ancestorT2); | |
return; | |
} | |
assertTrue(ancestorT1.equals(ancestorT2) && ancestorT2.equals(ancestorT1)); | |
} | |
private T newAncestorEqualInstanceCheckCorrect() | |
{ | |
final T ancestorT = newAncestorEqualInstance(); | |
if (ancestorT == null) | |
{ | |
assertTrue("newAncestorEqualInstance returned null, although the test class ", Modifier.isFinal(testClass.getModifiers())); | |
} else | |
{ | |
assertTrue("newAncestorEqualInstance returned object of class " + ancestorT.getClass() + ", which is not subtype of " | |
+ testClass.getName(), testClass.isAssignableFrom(ancestorT.getClass())); | |
} | |
return ancestorT; | |
} | |
private T newEqualInstanceCheckCorrect() | |
{ | |
final T objectT = newEqualInstance(); | |
assertTrue("newEqualInstance returned object of class " + objectT.getClass().getName() + "Expected was class " + testClass.getName(), | |
testClass == objectT.getClass()); | |
return objectT; | |
} | |
private T newNonEqualInstanceCheckCorrect() | |
{ | |
final T objectT = newNonEqualInstance(); | |
assertTrue("newNonEqualInstance returned object of class " + objectT.getClass().getName() + "Expected was class " + testClass.getName(), | |
testClass == objectT.getClass()); | |
return objectT; | |
} | |
} |