2009-05-07 15 views
21

La siguiente clase sirve como probador genérico para equals/hashCode contract. Es parte de un marco de prueba de cosecha propia.JUnit theory for hashCode/equals contract

  • ¿Qué opinas sobre?
  • ¿Cómo puedo (fuerte) probar esta clase?
  • ¿Es un buen uso de las teorías de Junit?

La clase:

@Ignore 
@RunWith(Theories.class) 
public abstract class ObjectTest { 

    // For any non-null reference value x, x.equals(x) should return true 
    @Theory 
    public void equalsIsReflexive(Object x) { 
     assumeThat(x, is(not(equalTo(null)))); 
     assertThat(x.equals(x), is(true)); 
    } 

    // For any non-null reference values x and y, x.equals(y) 
    // should return true if and only if y.equals(x) returns true. 
    @Theory 
    public void equalsIsSymmetric(Object x, Object y) { 
     assumeThat(x, is(not(equalTo(null)))); 
     assumeThat(y, is(not(equalTo(null)))); 
     assumeThat(y.equals(x), is(true)); 
     assertThat(x.equals(y), is(true)); 
    } 

    // 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. 
    @Theory 
    public void equalsIsTransitive(Object x, Object y, Object z) { 
     assumeThat(x, is(not(equalTo(null)))); 
     assumeThat(y, is(not(equalTo(null)))); 
     assumeThat(z, is(not(equalTo(null)))); 
     assumeThat(x.equals(y) && y.equals(z), is(true)); 
     assertThat(z.equals(x), is(true)); 
    } 

    // For any non-null reference values x and y, multiple invocations 
    // of x.equals(y) consistently return true or consistently return 
    // false, provided no information used in equals comparisons on 
    // the objects is modified. 
    @Theory 
    public void equalsIsConsistent(Object x, Object y) { 
     assumeThat(x, is(not(equalTo(null)))); 
     boolean alwaysTheSame = x.equals(y); 

     for (int i = 0; i < 30; i++) { 
      assertThat(x.equals(y), is(alwaysTheSame)); 
     } 
    } 

    // For any non-null reference value x, x.equals(null) should 
    // return false. 
    @Theory 
    public void equalsReturnFalseOnNull(Object x) { 
     assumeThat(x, is(not(equalTo(null)))); 
     assertThat(x.equals(null), is(false)); 
    } 

    // Whenever it is invoked on the same object more than once 
    // the hashCode() method must consistently return the same 
    // integer. 
    @Theory 
    public void hashCodeIsSelfConsistent(Object x) { 
     assumeThat(x, is(not(equalTo(null)))); 
     int alwaysTheSame = x.hashCode(); 

     for (int i = 0; i < 30; i++) { 
      assertThat(x.hashCode(), is(alwaysTheSame)); 
     } 
    } 

    // If two objects are equal according to the equals(Object) method, 
    // then calling the hashCode method on each of the two objects 
    // must produce the same integer result. 
    @Theory 
    public void hashCodeIsConsistentWithEquals(Object x, Object y) { 
     assumeThat(x, is(not(equalTo(null)))); 
     assumeThat(x.equals(y), is(true)); 
     assertThat(x.hashCode(), is(equalTo(y.hashCode()))); 
    } 

    // Test that x.equals(y) where x and y are the same datapoint 
    // instance works. User must provide datapoints that are not equal. 
    @Theory 
    public void equalsWorks(Object x, Object y) { 
     assumeThat(x, is(not(equalTo(null)))); 
     assumeThat(x == y, is(true)); 
     assertThat(x.equals(y), is(true)); 
    } 

    // Test that x.equals(y) where x and y are the same datapoint instance 
    // works. User must provide datapoints that are not equal. 
    @Theory 
    public void notEqualsWorks(Object x, Object y) { 
     assumeThat(x, is(not(equalTo(null)))); 
     assumeThat(x != y, is(true)); 
     assertThat(x.equals(y), is(false)); 
    } 
} 

uso:

import org.junit.experimental.theories.DataPoint; 

public class ObjectTestTest extends ObjectTest { 

    @DataPoint 
    public static String a = "a"; 
    @DataPoint 
    public static String b = "b"; 
    @DataPoint 
    public static String nullString = null; 
    @DataPoint 
    public static String emptyString = ""; 
} 
+0

Si estoy leyendo esto correctamente, ¿no debería la última declaración en su método equalsIsSimmetric ser assertThat, no asumir eso? –

+0

Sí, muchas gracias :) – dfa

+0

Así que, vas a buscar una solución interna, pero ¿conoces alguna biblioteca de código abierto para hacer este tipo de pruebas comunes?(También sugiero que sean comparables y serializables.) Me interesaría utilizar dicho marco. – ivo

Respuesta

8

Una cosa a tener en cuenta: probar la conformidad de un objeto con el contrato igual debe incluir instancias de otros tipos. En particular, es probable que surjan problemas con instancias de una subclase o una superclase. Joshua Bloch ofrece una excelente explicación de los errores relacionados en Effective Java (estoy reutilizando el enlace de Duffymo, por lo que debería obtener crédito por ello) - vea la sección bajo Transitivity que involucra las clases Point y ColorPoint.

Es cierto que su aplicación no impide que alguien escriba un examen que consiste en instancias de una subclase, sino porque ObjectTest es una clase genérica que da la impresión de que todos los puntos de datos deben provenir de una sola clase (la clase está probando) Puede ser mejor eliminar el parámetro de tipo por completo. Solo comida para pensar.

+0

de hecho! Gracias, estoy eliminando el parámetro de tipo T. – dfa

5

Joshua Bloch establece el contrato para el código hash y es igual en chapter 3 of "Effective Java". Parece que cubriste una gran cantidad de eso. Verifique el documento para ver si me perdí algo.

+1

también el javadoc para Object es muy detallado – dfa

0

Tal vez me falta algo, pero la prueba equalsIsSymmetric de hecho solo se prueba correctamente si se tienen DataPoints que tienen los mismos valores (por ejemplo, String a = "a"; String a2 = "a";) esta prueba solo se realiza cuando los 2 parámetros son una instancia (es decir, equalsIsSymmetric (a, a);). De hecho, prueba de nuevo si los iguales obedecen el requisito de "reflexión" en lugar del requisito simétrico.

+0

por esta razón la prueba tiene 'assumeThat (y.equals (x), es (verdadero))' – dfa

+0

sí, pero en la configuración actual no puede crear un ' x 'y a' y 'para los cuales x! = y y x.equals (y), porque la prueba de notEqualsWorks fallará en ese caso. Entonces, la prueba equalsIsSymmetric solo se realiza para x e y donde x == y. –

+0

sí. Asumiendo la configuración anterior, JUnit ejecutará: equalsIsSymmetric (a, a) y equalsIsSymmetric (b, b). ¿Derecha? – dfa

0

La teoría notEqualsWorks (Object x, Object y) es falsa: dos instancias distintas pueden seguir siendo lógicamente iguales según su método equals; está asumiendo que las instancias son lógicamente diferentes si son referencias diferentes.

Usando su propio ejemplo anterior, los dos puntos de datos distintas (por debajo de! A = a2) son sin embargo iguales, pero no pasan la prueba notEqualsWorks:

@DataPoint 
public static String a = "a"; 
@DataPoint 
public static String a2 = new String("a"); 
+0

cierto, pero debe tener en cuenta que la teoría tiene el siguiente requisito: "El usuario debe proporcionar los puntos de datos que no son iguales". – dfa

0

El método equalsWorks(Object x, Object y) está haciendo la misma prueba como equalsIsReflexive(Object x). Debe ser eliminado.

También creo que se debe eliminar notEqualsWorks(Object x, Object y), ya que evita que uno haga las otras teorías con puntos de datos que son iguales, aunque la totalidad de la prueba se basa en tener tales objetos.

Sin tales puntos de datos, la reflexividad es lo único que se prueba.