Pregunta

Tengo un conocimiento básico de los objetos simulados y falsos, pero no estoy seguro de tener una idea sobre cuándo y dónde usar la burla, especialmente en lo que se aplicaría a este escenario. aquí.

¿Fue útil?

Solución

Una prueba unitaria debe probar una única ruta de código mediante un único método.Cuando la ejecución de un método pasa fuera de ese método, a otro objeto y viceversa, se tiene una dependencia.

Cuando prueba esa ruta de código con la dependencia real, no está realizando una prueba unitaria;estás probando la integración.Si bien eso es bueno y necesario, no es una prueba unitaria.

Si su dependencia tiene errores, su prueba puede verse afectada de tal manera que arroje un falso positivo.Por ejemplo, puede pasar a la dependencia un valor nulo inesperado y es posible que la dependencia no genere un valor nulo como está documentado.Su prueba no encuentra una excepción de argumento nulo como debería y la prueba pasa.

Además, puede resultarle difícil, si no imposible, lograr de manera confiable que el objeto dependiente devuelva exactamente lo que desea durante una prueba.Eso también incluye generar excepciones esperadas dentro de las pruebas.

Un simulacro reemplaza esa dependencia.Usted establece expectativas sobre las llamadas al objeto dependiente, establece los valores de retorno exactos que debe proporcionarle para realizar la prueba que desea y/o qué excepciones lanzar para poder probar su código de manejo de excepciones.De esta forma podrás probar la unidad en cuestión fácilmente.

TL;DR:Burlarse de cada dependencia que toque su prueba unitaria.

Otros consejos

Los objetos simulados son útiles cuando quieres interacciones de prueba entre una clase bajo prueba y una interfaz particular.

Por ejemplo, queremos probar ese método. sendInvitations(MailServer mailServer) llamadas MailServer.createMessage() exactamente una vez, y también llama MailServer.sendMessage(m) exactamente una vez, y no se llama a ningún otro método en el MailServer interfaz.Aquí es cuando podemos usar objetos simulados.

Con objetos simulados, en lugar de pasar un real MailServerImpl, o una prueba TestMailServer, podemos pasar una implementación simulada del MailServer interfaz.Antes de pasar una burla MailServer, lo "entrenamos" para que sepa qué llamadas a métodos esperar y qué valores de retorno devolver.Al final, el objeto simulado afirma que todos los métodos esperados fueron llamados como se esperaba.

Esto suena bien en teoría, pero también tiene algunas desventajas.

Deficiencias simuladas

Si tiene un marco simulado, tendrá la tentación de utilizar un objeto simulado. cada vez debe pasar una interfaz a la clase bajo prueba.De esta manera terminas probar interacciones incluso cuando no es necesario.Desafortunadamente, las pruebas no deseadas (accidentales) de interacciones son malas, porque entonces estás probando que un requisito particular se implementa de una manera particular, en lugar de que la implementación produjo el resultado requerido.

Aquí hay un ejemplo en pseudocódigo.Supongamos que hemos creado un MySorter clase y queremos probarlo:

// the correct way of testing
testSort() {
    testList = [1, 7, 3, 8, 2] 
    MySorter.sort(testList)

    assert testList equals [1, 2, 3, 7, 8]
}


// incorrect, testing implementation
testSort() {
    testList = [1, 7, 3, 8, 2] 
    MySorter.sort(testList)

    assert that compare(1, 2) was called once 
    assert that compare(1, 3) was not called 
    assert that compare(2, 3) was called once 
    ....
}

(En este ejemplo asumimos que no es un algoritmo de clasificación particular, como la clasificación rápida, lo que queremos probar;en ese caso, la última prueba sería realmente válida).

En un ejemplo tan extremo, es obvio por qué el último ejemplo es incorrecto.Cuando cambiamos la implementación de MySorter, la primera prueba hace un gran trabajo al garantizar que aún ordenemos correctamente, que es el objetivo de las pruebas: nos permiten cambiar el código de forma segura.Por otra parte, esta última prueba siempre se rompe y es activamente dañino;dificulta la refactorización.

Se burla como talones

Los marcos simulados a menudo también permiten un uso menos estricto, donde no tenemos que especificar exactamente cuántas veces se deben llamar los métodos y qué parámetros se esperan;permiten crear objetos simulados que se utilizan como talones.

Supongamos que tenemos un método sendInvitations(PdfFormatter pdfFormatter, MailServer mailServer) que queremos probar.El PdfFormatter El objeto se puede utilizar para crear la invitación.Aquí está la prueba:

testInvitations() {
   // train as stub
   pdfFormatter = create mock of PdfFormatter
   let pdfFormatter.getCanvasWidth() returns 100
   let pdfFormatter.getCanvasHeight() returns 300
   let pdfFormatter.addText(x, y, text) returns true 
   let pdfFormatter.drawLine(line) does nothing

   // train as mock
   mailServer = create mock of MailServer
   expect mailServer.sendMail() called exactly once

   // do the test
   sendInvitations(pdfFormatter, mailServer)

   assert that all pdfFormatter expectations are met
   assert that all mailServer expectations are met
}

En este ejemplo, realmente no nos importa el PdfFormatter objeto, por lo que simplemente lo entrenamos para que acepte silenciosamente cualquier llamada y devuelva algunos valores de retorno predefinidos razonables para todos los métodos que sendInvitation() sucede que llama en este punto.¿Cómo se nos ocurrió exactamente esta lista de métodos para entrenar?Simplemente ejecutamos la prueba y seguimos agregando métodos hasta que la prueba pasó.Tenga en cuenta que entrenamos el código auxiliar para responder a un método sin tener idea de por qué necesita llamarlo, simplemente agregamos todo lo que se quejaba en la prueba.Estamos contentos, la prueba pasa.

Pero ¿qué pasa después, cuando cambiamos? sendInvitations(), o alguna otra clase que sendInvitations() usos, para crear archivos PDF más sofisticados?Nuestra prueba falla repentinamente porque ahora hay más métodos de PdfFormatter son llamados y no entrenamos nuestro trozo para esperarlos.Y normalmente no es sólo una prueba la que falla en situaciones como ésta, sino cualquier prueba que utilice, directa o indirectamente, el sendInvitations() método.Tenemos que arreglar todas esas pruebas agregando más entrenamientos.Observe también que no podemos eliminar métodos que ya no son necesarios porque no sabemos cuáles de ellos no son necesarios.Nuevamente, dificulta la refactorización.

Además, la legibilidad de la prueba sufrió terriblemente, hay mucho código que no escribimos porque quisiéramos, sino porque teníamos que hacerlo;No somos nosotros los que queremos ese código ahí.Las pruebas que utilizan objetos simulados parecen muy complejas y, a menudo, difíciles de leer.Las pruebas deben ayudar al lector a comprender cómo se debe utilizar la clase bajo prueba y, por lo tanto, deben ser simples y directas.Si no son legibles, nadie los va a mantener;de hecho, es más fácil eliminarlos que mantenerlos.

¿Cómo arreglar eso?Fácilmente:

  • Intente utilizar clases reales en lugar de simulacros siempre que sea posible.Usa lo real PdfFormatterImpl.Si no es posible, cambie las clases reales para que sea posible.No poder utilizar una clase en las pruebas suele indicar algunos problemas con la clase.Solucionar los problemas es una situación en la que todos ganan: usted arregló la clase y tendrá una prueba más simple.Por otro lado, no arreglarlo y usar simulaciones es una situación sin salida: no arreglaste la clase real y tienes pruebas más complejas y menos legibles que dificultan futuras refactorizaciones.
  • Intente crear una implementación de prueba simple de la interfaz en lugar de burlarse de ella en cada prueba y use esta clase de prueba en todas sus pruebas.Crear TestPdfFormatter eso no hace nada.De esa manera, puede cambiarlo una vez para todas las pruebas y sus pruebas no estarán abarrotadas de configuraciones largas en las que entrena sus talones.

Considerándolo todo, los objetos simulados tienen su utilidad, pero cuando no se usan con cuidado, a menudo fomentan malas prácticas, prueban detalles de implementación, dificultan la refactorización y producen pruebas difíciles de leer y de mantener..

Para obtener más detalles sobre las deficiencias de los simulacros, consulte también Objetos simulados:Deficiencias y casos de uso.

Regla de oro:

Si la función que está probando necesita un objeto complicado como parámetro, y sería complicado simplemente crear una instancia de este objeto (si, por ejemplo, intenta establecer una conexión TCP), utilice un simulacro.

Debes burlarte de un objeto cuando tienes una dependencia en una unidad de código que estás intentando probar y que debe ser "así".

Por ejemplo, cuando intentas probar alguna lógica en tu unidad de código pero necesitas obtener algo de otro objeto y lo que se devuelve de esta dependencia podría afectar lo que estás intentando probar, simula ese objeto.

Se puede encontrar un gran podcast sobre el tema. aquí

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