Created
January 15, 2010 16:50
-
-
Save rcampbell/278195 to your computer and use it in GitHub Desktop.
Java DSL for testing proper conformance to general contracts for common Object methods
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Any class which implements the <b>Comparable</b> interface must implement <b>compareTo</b>. Run | |
* these tests on all classes which implement <b>compareTo</b> to confirm they conform to the | |
* general contract. | |
* | |
* @author Robert Campbell | |
*/ | |
@SuppressWarnings("unchecked") | |
public class ComparableTests { | |
// Suppress default constructor for noninstantiability | |
private ComparableTests() { | |
throw new AssertionError(); | |
} | |
/** | |
* The implementor must ensure <b>signum(x.compareTo(y)) == -signum((y.compareTo(x)))</b> for | |
* all <b>x</b> and <b>y</b>. This implies that <b>x.compareTo(y)</b> must throw an exception if | |
* and only if <b>y.compareTo(x)</b> throws an exception. | |
* | |
* @param x | |
* , which should be a distinct instance from <b>y</b> | |
* @param y | |
* , which should be a distinct instance from <b>x</b> | |
* @return <b>true</b> if you reverse the direction of a comparison between the two object | |
* references and the expected thing happens: if the first object is less than the | |
* second, then the second must be greater than the first, and visa versa; <b>false</b> | |
* is returned otherwise | |
*/ | |
public static boolean isCompareToReversalConsistent(final Comparable x, final Comparable y) { | |
if (x == y) { | |
throw new IllegalArgumentException("Objects must be distinct instances"); | |
} | |
return Math.signum(x.compareTo(y)) == -Math.signum(y.compareTo(x)); | |
} | |
/** | |
* The implementor must ensure that the relation is transitive: <b>(x.compareTo(y) > 0 && | |
* y.compareTo(z) > 0)</b> implies <b>x.compareTo(z) > 0</b>. | |
* | |
* @param x | |
* should have a natural ordering preceding that of <b>y</b> | |
* @param y | |
* should have a natural ordering preceding that of <b>z</b> | |
* @param z | |
* should have a natural ordering following that of <b>x</b> | |
* @return <b>true</b> if <b>x</b> is greater than <b>y</b>, <b>y</b> is greater than <b>z</b>, | |
* and <b>x</b> is shown to be greater than <b>z</b> | |
*/ | |
public static boolean isCompareToTransitive(final Comparable x, final Comparable y, | |
final Comparable z) { | |
if ((x == y) || (y == z) || (x == z)) { | |
throw new IllegalArgumentException("Objects must be distinct instances"); | |
} | |
return (x.compareTo(y) < 0) && (y.compareTo(z) < 0) && (x.compareTo(z) < 0); | |
} | |
/** | |
* The implementor must ensure that <b>x.compareTo(y) == 0</b> implies that | |
* <b>signum(x.compareTo(z)) == signum(y.compareTo(z))</b>, for all <b>z</b>. | |
* | |
* @param x | |
* should have an equivalent natural order to <b>y</b>, but remain a distinct instance | |
* from the other arguments | |
* @param y | |
* should have an equivalent natural order to <b>x</b>, but remain a distinct instance | |
* from the other arguments | |
* @param z | |
* should be a distinct instance from the other arguments | |
* @return <b>true</b> when <b>x</b> and <b>y</b>, which compare as equal, yield the same | |
* results when compared to <b>z</b> | |
*/ | |
public static boolean compareToOrdersEqualObjectsEquallyAgainstZ(final Comparable x, | |
final Comparable y, final Comparable z) { | |
if ((x == y) || (y == z) || (x == z)) { | |
throw new IllegalArgumentException("Objects must be distinct instances"); | |
} | |
if (x.compareTo(y) != 0) { | |
throw new IllegalArgumentException("Objects must have an equivalent natural order"); | |
} | |
return (Math.signum(x.compareTo(z)) == Math.signum(y.compareTo(z))); | |
} | |
/** | |
* It is strongly recommended, but not strictly required, that <b>(x.compareTo(y) == 0) == | |
* (x.equals(y))</b>. Generally speaking, any class that implements the <b>Comparable</b> | |
* interface and violates this condition should clearly indicate this fact. The recommended | |
* language is "Note: This class has a natural ordering that is inconsistent with equals." | |
* | |
* @param x | |
* , which should be a distinct instance from <b>y</b>, but logically equal to <b>y</b> | |
* @param y | |
* , which should be a distinct instance from <b>x</b>, but logically equal to <b>x</b> | |
* @return <b>true</b> if <b>compareTo</b> is consistent with <b>equals</b>, <b>false</b> | |
* otherwise | |
*/ | |
public static boolean isCompareToConsistentWithEquals(final Comparable x, final Comparable y) { | |
if (x == y) { | |
throw new IllegalArgumentException("Objects must be distinct instances"); | |
} | |
if (x.equals(y) == false) { | |
throw new IllegalArgumentException("Objects must be logically equal"); | |
} | |
return (x.compareTo(y) == 0); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* You must obey the general contract when overriding <b>equals</b>. Run these tests on all classes | |
* which override <b>equals</b> to confirm they conform to the general contract. | |
* | |
* @author Robert Campbell | |
*/ | |
public class EqualsTests { | |
// Suppress default constructor for noninstantiability | |
private EqualsTests() { | |
throw new AssertionError(); | |
} | |
/** | |
* For any non-null reference value <b>x</b>, <b>x.equals(x)</b> must return <b>true</b>. | |
* | |
* @param x | |
* @return <b>true</b> if the equivalence relation is reflexive, <b>false</b> otherwise | |
*/ | |
public static boolean isEqualsReflexive(final Object x) { | |
return x.equals(x); | |
} | |
/** | |
* For any non-null reference values <b>x</b> and <b>y</b>, <b>x.equals(y)</b> must return | |
* <b>true</b> if and only if <b>y.equals(x)</b> returns <b>true</b>. | |
* | |
* @param x | |
* , which should be a distinct instance from <b>y</b>, but logically equal to <b>y</b> | |
* @param y | |
* , which should be a distinct instance from <b>x</b>, but logically equal to <b>x</b> | |
* @return <b>true</b> if the equivalence relation is symmetric, <b>false</b> otherwise | |
*/ | |
public static boolean isEqualsSymmetric(final Object x, final Object y) { | |
if (x == y) { | |
throw new IllegalArgumentException("Objects must be distinct instances"); | |
} | |
return x.equals(y) && y.equals(x); | |
} | |
/** | |
* For any non-null reference values <b>x</b>, <b>y</b>, <b>z</b>, if <b>x.equals(y)</b> returns | |
* <b>true</b> and <b>y.equals(z)</b> returns <b>true</b>, then <b>x.equals(z)</b> must return | |
* <b>true</b>. | |
* | |
* @param x | |
* should be a distinct instance from the other arguments | |
* @param y | |
* should be a distinct instance from the other arguments | |
* @param z | |
* should be a distinct instance from the other arguments | |
* @return <b>true</b> if the equivalence relation is transitive, <b>false</b> otherwise | |
*/ | |
public static boolean isEqualsTransitive(final Object x, final Object y, final Object z) { | |
if ((x == y) || (y == z) || (x == z)) { | |
throw new IllegalArgumentException("Objects must be distinct instances"); | |
} | |
return x.equals(y) && y.equals(z) && x.equals(z); | |
} | |
/** | |
* For any non-null reference values <b>x</b> and <b>y</b>, multiple invocations of | |
* <b>x.equals(y)</b> consistently return <b>true</b> or consistently return <b>false</b>, | |
* provided no information used in <b>equals</b> comparisons on the objects is modified. | |
* | |
* @param x | |
* , which should be a distinct instance from <b>y</b>, but logically equal to <b>y</b> | |
* @param y | |
* , which should be a distinct instance from <b>x</b>, but logically equal to <b>x</b> | |
* @return <b>true</b> if the equivalence relation's results are consistent over multiple | |
* invocations, <b>false</b> otherwise | |
*/ | |
public static boolean isEqualsConsistent(final Object x, final Object y) { | |
if (x == y) { | |
throw new IllegalArgumentException("Objects must be distinct instances"); | |
} | |
for (int i = 0; i < 1000; i++) { | |
if ((x.equals(y) == false) || (y.equals(x) == false)) { | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* For any non-null reference value <b>x</b>, <b>x.equals(null)</b> must return <b>false</b>. | |
* | |
* @param x | |
* @return <b>true</b> if <b>null</b> arguments are handled according to the specification for | |
* Object, <b>false</b> otherwise | |
*/ | |
public static boolean equalsCorrectlyHandlesNull(final Object x) { | |
return x.equals(null) == false; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* You must override <b>hashCode</b> in every class that overrides <b>equals</b>. Run these tests on | |
* all classes which override <b>hashCode</b> to confirm they conform to the general contract. | |
* | |
* @author Robert Campbell | |
*/ | |
public class HashCodeTests { | |
// Suppress default constructor for noninstantiability | |
private HashCodeTests() { | |
throw new AssertionError(); | |
} | |
/** | |
* Whenever it is invoked on the same object more than once during an execution of an | |
* application, the <b>hashCode</b> method must consistently return the same integer, provided | |
* no information used in <b>equals</b> comparisons on the object is modified. This integer need | |
* not remain consistent from one execution of an application to another execution of the same | |
* application. | |
* | |
* @param x | |
* @return <b>true</b> if the hashCode method consistently returns the same value over a large | |
* number of calls, <b>false</b> if the values are dissimilar | |
*/ | |
public static boolean isHashCodeConsistent(final Object x) { | |
final int initialHashCode = x.hashCode(); | |
for (int i = 0; i < 1000; i++) { | |
if (x.hashCode() != initialHashCode) { | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* If two objects are equal according to the <b>equals(Object)</b> method, then calling the | |
* <b>hashCode</b> method on each of the two objects must produce the same integer result. | |
* | |
* @param x | |
* , which should be logically equal to <b>y, but a distinct instance from <b>y</b> | |
* @param y | |
* , which should be logically equal to <b>x, but a distinct instance from <b>x</b> | |
* @return <b>true</b> if the logically equal object instances have matching hash code values, | |
* <b>false</b> if the hash code values are dissimilar | |
*/ | |
public static boolean haveEqualHashCodes(final Object x, final Object y) { | |
if ((x.equals(y) == false) || (y.equals(x) == false)) { | |
throw new IllegalArgumentException("Objects must be logically equal"); | |
} | |
if (x == y) { | |
throw new IllegalArgumentException("Objects must be distinct instances"); | |
} | |
return x.hashCode() == y.hashCode(); | |
} | |
/** | |
* It is not required that if two objects are unequal according to the <b>equals(Object)</b> | |
* method, then calling the <b>hashCode</b> method on each of the two objects must produce | |
* distinct integer results. However, the programmer should be aware that producing distinct | |
* integer results for unequal objects may improve the performance of hash tables. | |
* | |
* @param x | |
* , which should be a distinct instance from <b>y</b> and unequal to <b>y</b> | |
* @param y | |
* , which should be a distinct instance from <b>x</b> and unequal to <b>x</b> | |
* @return <b>true</b> if the unequal objects return unique hash code values, <b>false</b> if | |
* the hash code values are not unique | |
*/ | |
public static boolean haveUniqueHashCodes(final Object x, final Object y) { | |
if ((x.equals(y)) || (y.equals(x))) { | |
throw new IllegalArgumentException("Objects must not be logically equal"); | |
} | |
if (x == y) { | |
throw new IllegalArgumentException("Objects must be distinct instances"); | |
} | |
return x.hashCode() != y.hashCode(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*********************************************************** | |
***** EXAMPLE USAGE | |
***********************************************************/ | |
import static com.acme.common.ComparableTests.compareToOrdersEqualObjectsEquallyAgainstZ; | |
import static com.acme.common.ComparableTests.isCompareToConsistentWithEquals; | |
import static com.acme.common.ComparableTests.isCompareToReversalConsistent; | |
import static com.acme.common.ComparableTests.isCompareToTransitive; | |
import static com.acme.common.EqualsTests.equalsCorrectlyHandlesNull; | |
import static com.acme.common.EqualsTests.isEqualsConsistent; | |
import static com.acme.common.EqualsTests.isEqualsReflexive; | |
import static com.acme.common.EqualsTests.isEqualsSymmetric; | |
import static com.acme.common.EqualsTests.isEqualsTransitive; | |
import static com.acme.common.HashCodeTests.haveEqualHashCodes; | |
import static com.acme.common.HashCodeTests.haveUniqueHashCodes; | |
import static com.acme.common.HashCodeTests.isHashCodeConsistent; | |
import static org.testng.Assert.assertEquals; | |
import static org.testng.Assert.assertTrue; | |
import org.testng.annotations.Test; | |
import com.acme.Service; | |
@Test | |
public class ServiceTest { | |
@Test | |
public void testIdenticalServiceComparison() { | |
final Service service1 = newTestServiceX(); | |
final Service service2 = newTestServiceX(); | |
final int comparisonResult = service1.compareTo(service2); | |
final int objectsAreEqual = 0; | |
assertEquals(objectsAreEqual, comparisonResult); | |
} | |
@Test | |
public void testDifferentServiceComparison() { | |
final Service service1 = newTestServiceX(); | |
final Service service2 = newTestServiceY(); | |
final int comparisonResult = service1.compareTo(service2); | |
assertTrue(isLessThanSpecifiedObject(comparisonResult)); | |
} | |
@Test | |
public void confirmCompareToReversalIsConsistent() { | |
assertTrue(isCompareToReversalConsistent(newTestServiceX(), newTestServiceY())); | |
} | |
@Test | |
public void confirmCompareToIsTransitive() { | |
assertTrue(isCompareToTransitive(newTestServiceX(), newTestServiceY(), newTestServiceZ())); | |
} | |
@Test | |
public void confirmCompareToOrdersEqualObjectsEquallyAgainstZ() { | |
assertTrue(compareToOrdersEqualObjectsEquallyAgainstZ(newTestServiceX(), newTestServiceX(), | |
newTestServiceZ())); | |
} | |
@Test | |
public void confirmCompareToIsConsistentWithEquals() { | |
assertTrue(isCompareToConsistentWithEquals(newTestServiceX(), newTestServiceX())); | |
} | |
@Test | |
public void confirmEqualsIsReflexive() { | |
assertTrue(isEqualsReflexive(newTestServiceX())); | |
} | |
@Test | |
public void confirmEqualsIsSymmetric() { | |
assertTrue(isEqualsSymmetric(newTestServiceX(), newTestServiceX())); | |
} | |
@Test | |
public void confirmEqualsIsTransitive() { | |
assertTrue(isEqualsTransitive(newTestServiceX(), newTestServiceX(), newTestServiceX())); | |
} | |
@Test | |
public void confirmEqualsIsConsistent() { | |
assertTrue(isEqualsConsistent(newTestServiceX(), newTestServiceX())); | |
} | |
@Test | |
public void confirmEqualsHandlesNull() { | |
assertTrue(equalsCorrectlyHandlesNull(newTestServiceX())); | |
} | |
@Test | |
public void confirmHashCodeIsConsistent() { | |
assertTrue(isHashCodeConsistent(newTestServiceX())); | |
} | |
@Test | |
public void confirmEqualInstancesHaveEqualHashCodes() { | |
assertTrue(haveEqualHashCodes(newTestServiceX(), newTestServiceX())); | |
} | |
@Test | |
public void confirmUnequalInstancesHaveUnequalHashCodes() { | |
assertTrue( | |
haveUniqueHashCodes(newTestServiceX(), newTestServiceY()), | |
"While not an error, it is suggestive of an inferior hashCode implementation, which could result in poor hash table performance"); | |
} | |
private Boolean isLessThanSpecifiedObject(final int comparisonResult) { | |
return comparisonResult < 0; | |
} | |
private Service newTestServiceX() { | |
return new Service("IDC_P508", "Consumer Broadband and Mobile Services"); | |
} | |
private Service newTestServiceY() { | |
return new Service("IDC_P17912", "Financial Services Security"); | |
} | |
private Service newTestServiceZ() { | |
return new Service("IDC_P120", "Smart Handheld Devices"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment