2011-05-19 17 views
14

estoy escribiendo pruebas unitarias para los objetos que se clonan, por entregas, y/o escrita en un archivo XML. En los tres casos, me gustaría verificar que el objeto resultante sea el "mismo" que el original. He pasado por varias iteraciones en mi enfoque y habiendo encontrado fallas en todas ellas, me preguntaba qué hicieron otras personas.Comprobación de profunda igualdad en JUnit tests

Mi primera idea era implementar manualmente el método es igual en todas las clases, y el uso de assertEquals. Abandoné este enfoque después de decidir que anular equivale a realizar una comparación profunda en objetos mutables es algo malo, ya que casi siempre quiere que las colecciones utilicen la igualdad de referencia para los objetos mutables que contienen [1].

Entonces pensé que sólo pudiera cambiar el nombre del método para contentEquals o algo así. Sin embargo, después de pensar más, me di cuenta de que esto no me ayudaría a encontrar el tipo de regresiones que estaba buscando. Si un programador agrega un nuevo campo (mutable) y olvida agregarlo al método de clonación, entonces probablemente se olvide de agregarlo al método contentEquals también, y todas estas pruebas de regresión que estoy escribiendo no tendrán valor.

entonces me escribió una función assertContentEquals ingenioso que utiliza la reflexión para comprobar el valor de todos los miembros (no transitorios) de un objeto, de forma recursiva si es necesario. Esto evita los problemas con el método de comparación manual anterior, ya que asume por defecto que todos los campos deben conservarse y el programador debe declarar explícitamente los campos para omitir. Sin embargo, hay casos legítimos cuando un campo realmente no debería ser el mismo después de la clonación [2]. Puse un parámetro adicional toassertContentEquals que enumera qué campos ignorar, pero como esta lista se declara en la prueba unitaria, se vuelve realmente fea muy rápido en el caso de la comprobación recursiva.

Así que ahora estoy pensando en volver a incluir un método contentEquals en cada clase que se prueba, pero esta vez implementado utilizando una función auxiliar similar a assertContentsEquals descrito anteriormente. De esta forma, cuando se opera recursivamente, las exenciones se definirán en cada clase individual.

algun comentario? ¿Cómo se ha enfocado en este tema en el pasado?

Editado para exponer mis pensamientos:

[1] Tengo el racional para no primordial es igual en clases mutables de esta article. Una vez que pega un objeto mutable en un Set/Map, si un campo cambia, su hash cambiará pero su cubo no lo hará, rompiendo cosas. Entonces las opciones son no anular equals/getHash en objetos mutables o tener una política de nunca cambiar un objeto mutable una vez que se ha puesto en una colección.

No he mencionado que yo estoy poniendo en práctica estas pruebas de regresión en una base de código existente. En este contexto, la idea de cambiar la definición de iguales, y luego tener que encontrar todas las instancias donde podría cambiar el comportamiento del software me asusta. Siento que podría romper más fácilmente de lo que lo arreglo.

[2] Un ejemplo en nuestra base de código es una estructura gráfica, donde cada nodo necesita un identificador único a utilizar para unir el XML nodos cuando finalmente escrito a XML. Cuando clonamos estos objetos, queremos que el identificador sea diferente, pero que todo lo demás permanezca igual. Después de reflexionar más sobre esto, parece que las preguntas "¿este objeto ya está en esta colección?" Y "¿estos objetos se definen de la misma manera?", Utilizan conceptos de igualdad fundamentalmente diferentes en este contexto. El primero es preguntar acerca de la identidad y quisiera que se incluyera la identificación si se hace una comparación profunda, mientras que la segunda pregunta sobre la similitud y no quiero que se incluya la ID. Esto me hace inclinarme más contra la implementación del método de igualdad.

¿Están de acuerdo con esta decisión, o creen que implementar la igualdad es la mejor manera de hacerlo?

+0

Abandoné este enfoque después de decidir que anular equivale a realizar una comparación profunda de objetos mutables es una Lo malo es que casi siempre quiere que las colecciones usen la igualdad de referencia para los objetos mutables que contienen. Esto no es cierto: todas las clases de colecciones mutables anulan la igualdad. Aunque es responsabilidad de la clase individual anular el método de igualdad. – kuriouscoder

+1

Dejando a un lado, es posible que desee ver org.apache.commons.lang.builder.EqualsBuilder # reflectionEquals, suena muy similar a lo que implementó con su método contentEquals. – artbristol

+1

La sugerencia de @artbristol es buena, y con EqualsBuilder puede especificar qué campos se deben omitir en el método equals. Lo he implementado en nuestro proyecto y ha captado algunas cosas. Una cosa de la nota. En una prueba de rendimiento rápido, escribí comparando implementaciones equivalentes, reflectionEquals tomó 4x tan largo como pojomatic y 25x, siempre y cuando el eclipse generase el mismo método. También puede usar EqualsBuilder.append, que se generó entre pojomatic y eclipse. – digitaljoel

Respuesta

5

Me gustaría ir con el enfoque de reflexión y definir una anotación personalizada con RetentionPolicy.RUNTIME para permitir que los implementadores de las clases probadas marquen los campos que se espera que cambien después de la clonación. Luego puede verificar la anotación con reflejo y omitir los campos marcados.

De esta manera puede mantener su código de prueba genérico y simple y tener un medio conveniente para marcar excepciones directamente en el código sin afectar el diseño o el comportamiento en tiempo de ejecución del código que debe probarse.

La anotación podría tener este aspecto:

import java.lang.annotation.*; 

@Retention(RetentionPolicy.RUNTIME) 
@Target({ElementType.FIELD}) 
public @interface ChangesOnClone 
{ 
} 

Ésta es la forma en que se puede utilizar en el código que se va a probar:

class ABC 
{ 
    private String name; 

    @ChangesOnClone 
    private Cache cache; 
} 

Y por último, la parte correspondiente del código de prueba :

for (Field field : fields) 
{ 
    if(field.getAnnotation(ChangesOnClone.class)) 
     continue; 
    // else test it 
} 
+0

Si va con la reflexión, definitivamente NO implementaría su propia versión ya que existen implementaciones probadas y probadas, como EqualsBuilder y Pojomatic. – digitaljoel

+1

Usar anotaciones es una muy buena idea. También me permitirá manejar casos en los que la clonación de "igualdad" y la "igualdad" de serialización son diferentes. – pavon

+0

Sí, +1 para el enfoque de anotación. –

Cuestiones relacionadas