Soy nuevo en las pruebas unitarias y escucho continuamente las palabras "objetos simulados". En términos simples, ¿alguien puede explicar qué son los objetos simulados y para qué se usan típicamente al escribir pruebas unitarias?
Soy nuevo en las pruebas unitarias y escucho continuamente las palabras "objetos simulados". En términos simples, ¿alguien puede explicar qué son los objetos simulados y para qué se usan típicamente al escribir pruebas unitarias?
Respuestas:
Como usted dice que es nuevo en las pruebas unitarias y que solicita objetos simulados en "términos simples", intentaré con un ejemplo simple.
Imagine pruebas unitarias para este sistema:
cook <- waiter <- customer
En general, es fácil imaginar probar un componente de bajo nivel como cook
:
cook <- test driver
El conductor de prueba simplemente ordena diferentes platos y verifica que el cocinero devuelva el plato correcto para cada pedido.
Es más difícil probar un componente intermedio, como el camarero, que utiliza el comportamiento de otros componentes. Un probador ingenuo podría probar el componente de camarero de la misma manera que probamos el componente de cocción:
cook <- waiter <- test driver
El conductor de prueba pediría diferentes platos y se aseguraría de que el camarero devuelva el plato correcto. Desafortunadamente, eso significa que esta prueba del componente de camarero puede depender del comportamiento correcto del componente de cocción. Esta dependencia es aún peor si el componente de cocción tiene características poco amigables con la prueba, como un comportamiento no determinista (el menú incluye la sorpresa del chef como plato), muchas dependencias (el cocinero no cocinará sin todo su personal) o muchos recursos (algunos platos requieren ingredientes caros o tardan una hora en cocinarse).
Dado que esta es una prueba de camarero, idealmente, queremos probar solo al camarero, no al cocinero. Específicamente, queremos asegurarnos de que el camarero transmita el pedido del cliente al cocinero correctamente y entregue la comida del cocinero al cliente correctamente.
La prueba de unidad significa probar unidades de forma independiente, por lo que un mejor enfoque sería aislar el componente bajo prueba (el camarero) utilizando lo que Fowler llama dobles de prueba (maniquíes, trozos, falsificaciones, simulacros) .
-----------------------
| |
v |
test cook <- waiter <- test driver
Aquí, el cocinero de prueba está "confabulado" con el conductor de prueba. Idealmente, el sistema bajo prueba está diseñado para que el cocinero de prueba pueda ser fácilmente sustituido ( inyectado ) para trabajar con el camarero sin cambiar el código de producción (por ejemplo, sin cambiar el código del camarero).
Ahora, la prueba de cocción (prueba doble) podría implementarse de diferentes maneras:
Vea el artículo de Fowler para obtener más detalles sobre falsificaciones vs trozos vs simulacros vs tontos , pero por ahora, centrémonos en un simulador de cocina.
-----------------------
| |
v |
mock cook <- waiter <- test driver
Una gran parte de la unidad que prueba el componente de camarero se centra en cómo interactúa el camarero con el componente de cocción. Un enfoque basado en simulacro se centra en especificar completamente cuál es la interacción correcta y detectar cuándo sale mal.
El objeto simulado sabe de antemano qué se supone que sucederá durante la prueba (por ejemplo, cuál de sus métodos se invocarán las llamadas, etc.) y el objeto simulado sabe cómo se supone que reaccionará (por ejemplo, qué valor de retorno proporcionar). El simulacro indicará si lo que realmente sucede difiere de lo que se supone que debe suceder. Se podría crear un objeto simulado personalizado desde cero para cada caso de prueba para ejecutar el comportamiento esperado para ese caso de prueba, pero un marco de simulación se esfuerza por permitir que dicha especificación de comportamiento se indique clara y fácilmente directamente en el caso de prueba.
La conversación en torno a una prueba simulada podría verse así:
prueba del conductor para burlarse del cocinero : espera una orden de hot dog y dale este hot dog ficticio en respuesta
prueba del conductor (haciéndose pasar por el cliente) al camarero : me gustaría un perrito caliente, por favor,
camarero para burlarse del cocinero : 1 perro caliente, por favor,
burlarse del cocinero al camarero : pedir: 1 perro caliente listo (le da el perro caliente falso al camarero)
camarero para probar al conductor : aquí está su hot dog (le da un hot dog ficticio al conductor de prueba)piloto de prueba : ¡PRUEBA EXITOSA!
Pero como nuestro camarero es nuevo, esto es lo que podría suceder:
prueba del conductor para burlarse del cocinero : espera una orden de hot dog y dale este hot dog ficticio en respuesta
prueba del conductor (haciéndose pasar por el cliente) para el camarero : me gustaría un hot dog, por favor, el
camarero para burlarse del cocinero : 1 hamburguesa, por favor, el
cocinero falso detiene la prueba: me dijeron que esperar una orden de perritos calientes!el controlador de prueba observa el problema: ¡PRUEBA FALLIDA! - el camarero cambió el orden
o
prueba del conductor para burlarse del cocinero : espera una orden de hot dog y dale este hot dog ficticio en respuesta
prueba del conductor (haciéndose pasar por el cliente) al camarero : me gustaría un perrito caliente, por favor,
camarero para burlarse del cocinero : 1 perro caliente, por favor,
burlarse del cocinero al camarero : pedir: 1 perro caliente listo (le da el perro caliente falso al camarero)
camarero para probar al conductor : aquí están tus papas fritas (da papas fritas de otro orden para probar el controlador)el conductor de prueba observa las inesperadas papas fritas: ¡LA PRUEBA FALLÓ! el camarero devolvió el plato equivocado
Puede ser difícil ver claramente la diferencia entre objetos simulados y trozos sin un ejemplo contrastante basado en trozos para ir con esto, pero esta respuesta ya es demasiado larga :-)
También tenga en cuenta que este es un ejemplo bastante simplista y que los marcos de imitación permiten algunas especificaciones bastante sofisticadas del comportamiento esperado de los componentes para admitir pruebas exhaustivas. Hay mucho material sobre objetos simulados y marcos burlones para obtener más información.
Un objeto simulado es un objeto que sustituye a un objeto real. En la programación orientada a objetos, los objetos simulados son objetos simulados que imitan el comportamiento de los objetos reales de manera controlada.
Un programador de computadora generalmente crea un objeto simulado para probar el comportamiento de algún otro objeto, de la misma manera que un diseñador de automóviles usa un maniquí de prueba de choque para simular el comportamiento dinámico de un humano en los impactos de vehículos.
http://en.wikipedia.org/wiki/Mock_object
Los objetos simulados le permiten configurar escenarios de prueba sin tener que soportar recursos grandes y difíciles de manejar, como bases de datos. En lugar de llamar a una base de datos para realizar pruebas, puede simular su base de datos utilizando un objeto simulado en sus pruebas unitarias. Esto lo libera de la carga de tener que configurar y destruir una base de datos real, solo para probar un método único en su clase.
La palabra "simulacro" a veces se usa erróneamente de manera intercambiable con "trozo". Las diferencias entre las dos palabras se describen aquí. Esencialmente, un simulacro es un objeto auxiliar que también incluye las expectativas (es decir, "aserciones") para el comportamiento adecuado del objeto / método bajo prueba.
Por ejemplo:
class OrderInteractionTester...
public void testOrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
Mock warehouse = mock(Warehouse.class);
Mock mailer = mock(MailService.class);
order.setMailer((MailService) mailer.proxy());
mailer.expects(once()).method("send");
warehouse.expects(once()).method("hasInventory")
.withAnyArguments()
.will(returnValue(false));
order.fill((Warehouse) warehouse.proxy());
}
}
Observe que los objetos simulados warehouse
y mailer
están programados con los resultados esperados.
Los objetos simulados son objetos simulados que imitan el comportamiento de los reales. Por lo general, escribe un objeto simulado si:
Un objeto simulado es un tipo de prueba doble . Está utilizando mockobjects para probar y verificar el protocolo / interacción de la clase bajo prueba con otras clases.
Por lo general, tendrá una especie de 'programa' o 'registro' de expectativas: llamadas de método que espera que su clase le haga a un objeto subyacente.
Digamos, por ejemplo, que estamos probando un método de servicio para actualizar un campo en un widget. Y que en su arquitectura hay un WidgetDAO que se ocupa de la base de datos. Hablar con la base de datos es lento y configurarlo y limpiarlo después es complicado, por lo que nos burlaremos del WidgetDao.
pensemos qué debe hacer el servicio: debe obtener un widget de la base de datos, hacer algo con él y guardarlo nuevamente.
Entonces, en pseudo-idioma con una biblioteca de pseudo-simulacro, tendríamos algo como:
Widget sampleWidget = new Widget();
WidgetDao mock = createMock(WidgetDao.class);
WidgetService svc = new WidgetService(mock);
// record expected calls on the dao
expect(mock.getById(id)).andReturn(sampleWidget);
expect(mock.save(sampleWidget);
// turn the dao in replay mode
replay(mock);
svc.updateWidgetPrice(id,newPrice);
verify(mock); // verify the expected calls were made
assertEquals(newPrice,sampleWidget.getPrice());
De esta manera, podemos probar fácilmente el desarrollo de clases que dependen de otras clases.
Recomiendo encarecidamente un gran artículo de Martin Fowler que explique qué son exactamente los simulacros y en qué se diferencian de los trozos.
Cuando la unidad prueba alguna parte de un programa de computadora, lo ideal es probar solo el comportamiento de esa parte en particular.
Por ejemplo, mire el pseudocódigo siguiente de una pieza imaginaria de un programa que usa otro programa para llamar a print algo:
If theUserIsFred then
Call Printer(HelloFred)
Else
Call Printer(YouAreNotFred)
End
Si estuviera probando esto, principalmente querría probar la parte que analiza si el usuario es Fred o no. Realmente no quieres probar la Printer
parte de las cosas. Esa sería otra prueba.
Aquí es donde entran los objetos simulados. Pretenden ser otro tipo de cosas. En este caso, usaría un Mock Printer
para que actuara como una impresora real, pero no haría cosas inconvenientes como imprimir.
Hay varios otros tipos de objetos de simulación que puede usar que no son simulacros. Lo principal que hace que Mocks Mocks es que se pueden configurar con comportamientos y expectativas.
Las expectativas le permiten a tu simulacro generar un error cuando se usa incorrectamente. Entonces, en el ejemplo anterior, puede estar seguro de que se llama a la impresora con HelloFred en el caso de prueba "el usuario es Fred". Si eso no sucede, tu Mock puede advertirte.
Comportamiento en simulacros significa que, por ejemplo, su código hizo algo como:
If Call Printer(HelloFred) Returned SaidHello Then
Do Something
End
Ahora desea probar qué hace su código cuando se llama a la Impresora y devuelve SaidHello, de modo que puede configurar el Mock para que devuelva SaidHello cuando se llama con HelloFred.
Un buen recurso en torno a esto es Martin Fowlers post Mocks An't Stubs
Los objetos simulados y trozos son una parte crucial de las pruebas unitarias. De hecho, hacen un largo camino para asegurarse de que esté probando unidades , en lugar de grupos de unidades.
En pocas palabras, usa stubs para romper la dependencia de SUT (Sistema bajo prueba) en otros objetos y simulacros para hacer eso y verificar que SUT haya llamado ciertos métodos / propiedades en la dependencia. Esto se remonta a los principios fundamentales de las pruebas unitarias: que las pruebas deben ser fáciles de leer, rápidas y no requieren configuración, lo que puede implicar el uso de todas las clases reales.
En general, puede tener más de un trozo en su prueba, pero solo debe tener un simulacro. Esto se debe a que el propósito del simulacro es verificar el comportamiento y su prueba solo debe probar una cosa.
Escenario simple usando C # y Moq:
public interface IInput {
object Read();
}
public interface IOutput {
void Write(object data);
}
class SUT {
IInput input;
IOutput output;
public SUT (IInput input, IOutput output) {
this.input = input;
this.output = output;
}
void ReadAndWrite() {
var data = input.Read();
output.Write(data);
}
}
[TestMethod]
public void ReadAndWriteShouldWriteSameObjectAsRead() {
//we want to verify that SUT writes to the output interface
//input is a stub, since we don't record any expectations
Mock<IInput> input = new Mock<IInput>();
//output is a mock, because we want to verify some behavior on it.
Mock<IOutput> output = new Mock<IOutput>();
var data = new object();
input.Setup(i=>i.Read()).Returns(data);
var sut = new SUT(input.Object, output.Object);
//calling verify on a mock object makes the object a mock, with respect to method being verified.
output.Verify(o=>o.Write(data));
}
En el ejemplo anterior, utilicé Moq para demostrar trozos y simulacros. Moq usa la misma clase para ambos, Mock<T>
lo que lo hace un poco confuso. Independientemente, en tiempo de ejecución, la prueba fallará si output.Write
no se llama con datos como parameter
, mientras que la falla en la llamada input.Read()
no fallará.
Como sugirió otra respuesta a través de un enlace a "Los simulacros no son trozos ", los son una forma de "prueba doble" para usar en lugar de un objeto real. Lo que los hace diferentes de otras formas de dobles de prueba, como los objetos de código auxiliar, es que otros dobles de prueba ofrecen verificación de estado (y opcionalmente simulación) mientras que los simulacros ofrecen verificación de comportamiento (y opcionalmente simulación).
Con un apéndice, puede llamar a varios métodos en el apéndice en cualquier orden (o incluso de forma repetitiva) y determinar el éxito si el apéndice ha capturado un valor o estado que deseaba. En contraste, un objeto simulado espera que se invoquen funciones muy específicas, en un orden específico e incluso un número específico de veces. La prueba con un objeto simulado se considerará "fallida" simplemente porque los métodos se invocaron en una secuencia o recuento diferente, ¡incluso si el objeto simulado tenía el estado correcto cuando concluyó la prueba!
De esta manera, los objetos simulados a menudo se consideran más estrechamente acoplados al código SUT que los objetos de código auxiliar. Eso puede ser algo bueno o malo, dependiendo de lo que intente verificar.
Parte del punto de usar objetos simulados es que no tienen que implementarse realmente de acuerdo con las especificaciones. Solo pueden dar respuestas ficticias. Por ejemplo, si tiene que implementar los componentes A y B, y ambos "se llaman" (interactúan) entre sí, entonces no puede probar A hasta que se implemente B, y viceversa. En el desarrollo basado en pruebas, este es un problema. Entonces creas objetos simulados ("ficticios") para A y B, que son muy simples, pero dan algún tipo de respuesta cuando interactúan con ellos. De esa manera, puede implementar y probar A usando un objeto simulado para B.
Para php y phpunit se explica bien en phpunit documentaion. ver aquí la documentación de phpunit
En palabras simples, el objeto de burla es solo un objeto ficticio del original y devuelve su valor de retorno, este valor de retorno se puede usar en la clase de prueba
Es una de las principales perspectivas de las pruebas unitarias. Sí, está intentando probar su única unidad de código y los resultados de su prueba no deberían ser relevantes para el comportamiento de otros beans u objetos. por lo tanto, debe burlarse de ellos utilizando objetos Mock con alguna respuesta correspondiente simplificada.