Pregunta

Esta es una pregunta general sobre cómo hacer una prueba unitaria de una clase Java utilizando objetos simulados.
Puedo resumir mi problema con este ejemplo.
Digamos que tengo una interfaz llamada MyInterface.java y un "TwoString". Objeto que no anula equals ()

" TwoString.java "

   private String string1;
   private String string2;

   public TwoString(String string1, String string2) {
     this.string1 = string1;
     this.string2 = string2;
   }
   ...getters..setters..

"MyInterface.java"

void callMe(TwoString twoString);

Entonces tengo " MyClass.java " Object. Su constructor acepta una implementación concreta de MyInterface.
MyClass methodToTest () contiene la lógica para crear un objeto TwoString de alguna manera. Digamos que se creará como

new TwoString("a","b")

Entonces, cuando se llama a methodToTest (), crea este objeto TwoString que se pasará al método de interfaz callMe (TwoString twoString) .

Básicamente quiero burlarme de la interfaz. Crea un objeto MyClass con este simulacro. Luego verifique que se llame al método simulado con una instancia específica de TwoString.

Estoy usando EasyMock y este es un código de Java

"MyClassTest.java"

public void test() throws Exception {   
   MyInterface myInterfaceMock = createMock(MyInterface.class);
   MyClass myClass = new MyClass(myInterfaceMock);

   myInterfaceMock.callMe(new TwoString("a","b"));   <--- fails here
   expectLastCall();
   replay(myInterfaceMock);

   myClass.methodToTest();
   verify(myInterfaceMock);

Aquí viene el problema. El objeto TwoString que espero en la llamada

myInterfaceMock.callMe(new TwoString("a","b"));

es diferente del generado en MyClass.methodToTest () porque TwoString.java no anula iguales.

Puedo omitir el problema en la instancia de TwoString usando

myInterfaceMock.callMe((TwoString)anyObject());

pero quiero asegurarme de que se llama al método de interfaz con una instancia específica de TwoString que contiene " a " como string1 y " b " como cadena2.

En este caso, el objeto TwoString es muy simple y será fácil anular el método de igualdad, pero ¿y si necesito verificar un objeto más complejo?

Gracias

editar:

Intentaré hacerlo más legible con este ejemplo

public class MyClassTest {
    private MyClass myClass;
    private TaskExecutor taskExecutorMock;

    @Before
    public void setUp() throws Exception {
        taskExecutorMock = createMock(TaskExecutor.class);
        myClass = new MyClass(taskExecutorMock);
    }

    @Test
    public void testRun() throws Exception {
        List<MyObj> myObjList = new ArrayList<MyObj>();
        myObjList.add(new MyObj("abc", referenceToSomethingElse));

        taskExecutorMock.execute(new SomeTask(referenceToSomethingElse,  ???new SomeObj("abc", referenceToSomethingElse, "whatever")));   <--- ??? = object created using data in myObjList
        expectLastCall();
        replay(taskExecutorMock);

        myClass.run(myObjList);

        verify(taskExecutorMock);
    }
}

??? SomeObj = objeto creado por myClass.run () utilizando datos contenidos en myObjList.
Digamos que SomeObj proviene de una biblioteca de terceros y no anula los iguales.

Quiero asegurarme de que se llama al método taskExecutorMock.execute () con una instancia específica de ese SomeObj

¿Cómo puedo probar que myClass.run () en realidad está llamando al método taskExecutorMock con una instancia correcta

¿Fue útil?

Solución

Es posible usar un método personalizado de igualación de igualdad usando org.easymock.IArgumentMatcher .

Debería verse algo así como:

private static <T extends TwoString> T eqTwoString(final TwoString twoString) {
    reportMatcher(new IArgumentMatcher() {
        /** Required to get nice output */
        public void appendTo(StringBuffer buffer) {
            buffer.append("eqTwoString(" + twoString.getString1() + "," + twoString.getString2() + ")");
        }

        /** Implement equals basically */
        public boolean matches(Object object) {
            if (object instanceof TwoString) {
                TwoString other = (TwoString) object;
                // Consider adding null checks below
                return twoString.getString1().equals(object.getString1()) && twoString.getString2().equals(object.getString2());
            }
            else {
                return false;
            }
        }
    });
    return null;
}

Y se usa de la siguiente manera:

myInterfaceMock.callMe(eqTwoString(new TwoString("a","b")));

Algunos detalles pueden no ser correctos, pero en esencia es como lo he hecho antes. Hay otro ejemplo y explicaciones más completas disponibles en la documentación de EasyMock . Simplemente busque IArgumentMatcher .

Otros consejos

Primero: probablemente te refieres a "anular igual" en lugar de implementar, ya que todas las clases tienen alguna implementación de iguales (la que heredan de Object si no anulan nada más).

La respuesta en este caso es simple: todos los objetos de valor realmente deberían implementar equals y hashcode. Ya sea uno simple como TwoString, o el objeto más complejo al que alude, debe ser responsabilidad del objeto verificar si es igual a otro objeto.

La única otra alternativa sería básicamente deconstruir el objeto en su código de prueba, así que en lugar de

assertEquals(expected, twoStr);

lo harías

assertEquals(expected.getStringOne(), twoStr.getStringOne());
assertEquals(expected.getStringTwo(), twoStr.getStringTwo());

Espero que pueda ver que esto es malo en al menos tres formas. En primer lugar, básicamente estás duplicando la lógica que debería estar en el método equals () propio de la clase; y en cualquier lugar que desee comparar estos objetos, tendrá que escribir el mismo código.

En segundo lugar, solo puede ver el estado público del objeto, bien podría haber algún estado privado que haga que dos aparentemente objetos similares no sean iguales (por ejemplo, una clase Lift podría tener un público accesible " piso '', pero privado '' subiendo o bajando '' también).

Finalmente, es una violación de la Ley de Demeter que una clase de un tercero básicamente se esté metiendo con los internos de TwoString tratando de determinar si las cosas son iguales.

El objeto mismo debe implementar su propio método equals (), puro y simple.

Eche un vistazo a Jakarta Commons Lang: EqualsBuilder.reflectionEquals ()

Si bien estoy de acuerdo con dtsazza en que todos los objetos de valor deben tener un método equals () (y hashCode () ), son no siempre es apropiado para las pruebas: la mayoría de los objetos de valor basarán la igualdad en una clave, en lugar de en todos los campos.

Al mismo tiempo, desconfío de cualquier prueba que quiera verificar todos los campos: me parece que estoy diciendo "asegúrese de que este método no haya cambiado algo que no estaba planeando que cambiara". . " Es una prueba válida y, en cierto nivel, una prueba muy bien intencionada, pero da un poco de miedo que sientas que la necesitas.

  

En este caso, el objeto TwoString es muy simple y será fácil anular el método de igualdad, pero ¿y si necesito verificar un objeto más complejo?

Una vez que sus objetos comienzan a volverse tan complejos que no puede verificar trivialmente si son iguales desde otro lugar, probablemente debería refactorizarlos e inyectarlos como una dependencia. Esto cambiaría el diseño, pero generalmente eso es para mejor.

También parece estar confiando en algún conocimiento del comportamiento interno de sus clases. Lo anterior es una prueba de interacción entre dos clases, que aún funciona, pero cuanto más grande sea su conjunto de componentes probados, menos podrá hablar de "unidad". pruebas En cierto punto, abandonas el ámbito de las pruebas unitarias y comienzas a hacer pruebas de integración, en cuyo caso podrías estar mejor con un arnés de prueba completo y un comportamiento de aislamiento en ciertos lugares ...

Puede lograr esto con los captores de argumentos en Mockito 1.8.

http: // mockito .googlecode.com / svn / sucursales / 1.8.0 / javadoc / org / mockito / Mockito.html # 15

¡Sé que estás usando EasyMock, pero cambiar a Mockito es fácil y es mucho mejor!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top