Skip to content

Instantly share code, notes, and snippets.

@rcampbell
Created January 15, 2010 16:50
Show Gist options
  • Save rcampbell/278195 to your computer and use it in GitHub Desktop.
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
/**
* 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);
}
}
/**
* 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;
}
}
/**
* 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();
}
}
/***********************************************************
***** 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