unit testing - unit - ¿Cuál es el propósito de los objetos simulados?
tdd (10)
Soy nuevo en las pruebas unitarias y continuamente escucho las palabras "objetos falsos". En términos sencillos, ¿alguien puede explicar qué son los objetos falsos y para qué se usan normalmente cuando se escriben pruebas unitarias?
Como otra respuesta sugerida a través de un enlace a " Mocks Are not Stubs ", los simulacros 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 stub, es que otros dobles de prueba ofrecen verificación de estado (y opcionalmente simulación) mientras que los simuladores ofrecen verificación de comportamiento (y opcionalmente simulación).
Con un stub, puede llamar a varios métodos en el stub en cualquier orden (o incluso de manera repetitiva) y determinar el éxito si el stub ha capturado un valor o estado que pretendía. Por el contrario, un objeto simulado espera que se llamen funciones muy específicas, en un orden específico, e incluso un número específico de veces. La prueba con un objeto falso 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 tipo stub. Eso puede ser bueno o malo, dependiendo de lo que intentes verificar.
Cuando la unidad está probando alguna parte de un programa de computadora, lo ideal es que pruebe solo el comportamiento de esa parte en particular.
Por ejemplo, observe el pseudocódigo que se muestra a continuación en una pieza imaginaria de un programa que usa otro programa para imprimir 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 desea probar la Printer
parte de las cosas. Esa sería otra prueba.
Aquí es donde entran los objetos simulados. Fingen ser otro tipo de cosas. En este caso, utilizaría una Printer
simulada para que actuara como una impresora real, pero no haría cosas inconvenientes como la impresión.
Hay varios otros tipos de objetos simulados que puede usar que no son Mocks. Lo principal que hace que Mocks Mocks sea que se pueden configurar con comportamientos y expectativas.
Las expectativas le permiten a su simulacro generar un error cuando se usa incorrectamente. Por lo tanto, en el ejemplo anterior, puede asegurarse de que la impresora se llame con HelloFred en el caso de prueba "el usuario es Fred". Si eso no sucede, tu Mock puede advertirte.
Comportamiento en Mocks significa que, por ejemplo, de su código hizo algo como:
If Call Printer(HelloFred) Returned SaidHello Then
Do Something
End
Ahora quiere probar qué hace su código cuando se llama a la impresora y devuelve SaidHello, por lo que puede configurar el simulacro para devolver SaidHello cuando se llame con HelloFred.
Un buen recurso en torno a esto es la publicación de Martin Fowlers Las burlas no son talones
Dado que dices que eres nuevo en las pruebas unitarias y que te han preguntado por objetos simulados en "términos comunes", voy a probar el ejemplo de un profano.
Examen de la unidad
Imagine las pruebas unitarias para este sistema:
cook <- waiter <- customer
En general, es fácil imaginar probar un componente de bajo nivel como el cook
:
cook <- test driver
El conductor de la 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 del camarero de la misma manera que probamos el componente de cocción:
cook <- waiter <- test driver
El conductor de la prueba ordenaría platos diferentes y se aseguraría de que el camarero devuelva el plato correcto. Desafortunadamente, eso significa que esta prueba del componente camarero puede depender del comportamiento correcto del componente de cocción. Esta dependencia es aún peor si el componente de cocción tiene alguna característica antipática, como el comportamiento no determinista (el menú incluye la sorpresa del chef como plato), muchas dependencias (el cocinero no cocinará sin todo su personal) o mucho recursos (algunos platos requieren ingredientes caros o toman una hora para cocinar).
Ya 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 correctamente la orden del cliente al cocinero y entregue la comida del cocinero al cliente correctamente.
Pruebas unitarias significa probar unidades de forma independiente, por lo que un mejor enfoque sería aislar el componente bajo prueba (el camarero) usando lo que Fowler llama dobles de prueba (maniquíes, stubs, falsificaciones, burlas) .
-----------------------
| |
v |
test cook <- waiter <- test driver
Aquí, el cocinero de prueba está "en connivencia" con el conductor de prueba. Idealmente, el sistema bajo prueba está diseñado para que el cocinero de prueba pueda ser fácilmente sustituido ( injected ) para trabajar con el camarero sin cambiar el código de producción (por ejemplo, sin cambiar el código del camarero).
Simulacros de objetos
Ahora, el cocinero de prueba (prueba doble) podría implementarse de diferentes maneras:
- una cocinera falsa: alguien que pretende ser cocinero usando cenas congeladas y un microondas,
- un stub cook - un vendedor de perros calientes que siempre te da perros calientes sin importar lo que pidas, o
- un cocinero simulado: un policía encubierto siguiendo un guión que pretende ser un cocinero en una operación encubierta.
Ver el artículo de Fowler para más detalles sobre falsificaciones vs stubs vs mocks vs dummies , pero por ahora, centrémonos en un simulacro de cocina.
-----------------------
| |
v |
mock cook <- waiter <- test driver
Una gran parte de las pruebas unitarias del componente camarero se centra en cómo el camarero interactúa con el componente de cocción. Un enfoque basado en simulacros se enfoca en especificar completamente cuál es la interacción correcta y detectar cuándo sale mal.
El objeto simulado sabe de antemano lo que se supone que debe ocurrir durante la prueba (por ejemplo, cuál de sus métodos se invocarán llamadas, etc.) y el objeto simulado sabe cómo se supone que debe reaccionar (por ejemplo, qué valor de retorno proporcionar). El simulacro indicará si lo que realmente sucede difiere de lo que se supone que sucederá. Se puede 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í:
probar al conductor para que se burle del cocinero : espere un pedido de perrito caliente y dele este hot dog ficticio en respuesta
piloto de prueba (haciéndose pasar por cliente) al camarero : me gustaría un perro caliente por favor
camarero para burlarse del cocinero : 1 perro caliente por favor
simulacro de cocinero a camarero : ordenar: 1 perrito caliente listo (le da un perrito caliente al camarero)
mesero para probar el conductor : aquí está su perro caliente (le da un perrito caliente al conductor de prueba)piloto de prueba : ¡LA PRUEBA FUE EXITOSA!
Pero como nuestro camarero es nuevo, esto es lo que podría pasar:
probar al conductor para que se burle del cocinero : espere un pedido de perrito caliente y dele este hot dog ficticio en respuesta
piloto de prueba (haciéndose pasar por cliente) al camarero : me gustaría un perro caliente por favor
mesero para burlarse del cocinero : 1 hamburguesa por favor
el cocinero simulado detiene la prueba: ¡ me dijeron que debía esperar una orden de perrito caliente!el conductor de la prueba toma nota del problema: ¡LA PRUEBA FALLÓ! - el camarero cambió la orden
o
probar al conductor para que se burle del cocinero : espere un pedido de perrito caliente y dele este hot dog ficticio en respuesta
piloto de prueba (haciéndose pasar por cliente) al camarero : me gustaría un perro caliente por favor
camarero para burlarse del cocinero : 1 perro caliente por favor
simulacro de cocinero a camarero : ordenar: 1 perrito caliente listo (le da un perrito caliente al camarero)
mesero para probar el controlador : aquí está su papas fritas (da papas fritas de otro orden para probar el controlador)el conductor de la prueba toma nota de las papas fritas inesperadas: ¡LA PRUEBA FALLÓ! el camarero devolvió el plato equivocado
Puede ser difícil ver claramente la diferencia entre objetos simulados y talones sin un ejemplo contrastado 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 burlones permiten algunas especificaciones bastante sofisticadas del comportamiento esperado de los componentes para respaldar pruebas integrales. Hay mucho material sobre objetos falsos y marcos burlones para obtener más información.
Los objetos simulados son objetos simulados que imitan el comportamiento de los reales. Normalmente, escribes un objeto simulado si:
- El objeto real es demasiado complejo para incorporarlo en una prueba unitaria (por ejemplo, una comunicación de red, puede tener un objeto simulado que simule haber sido el otro par)
- El resultado de su objeto no es determinista
- El objeto real aún no está disponible
Los objetos simulados y de trozo son una parte crucial de las pruebas unitarias. De hecho, recorren 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 (System Under Test) en otros objetos y se burla de eso y verifica que SUT llama ciertos métodos / propiedades en la dependencia. Esto se remonta a los principios fundamentales de las pruebas unitarias: que las pruebas sean fáciles de leer, rápidas y no requieran configuración, lo que podría implicar el uso de todas las clases reales.
En general, puede tener más de un stub en su prueba, pero solo debe tener un simulacro. Esto se debe a que el propósito de la prueba 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 talones y burlas. Moq usa la misma clase para ambos: Mock<T>
lo que lo hace un poco confuso. De todos modos, en tiempo de ejecución, la prueba fallará si output.Write
no se llama con los datos como parameter
, mientras que la falla al invocar input.Read()
no le fallará.
Para php y phpunit está bien explicado en phpunit documentation. ver aquí la documentación de phpunit
En palabras simples, el objeto de burla es solo un objeto ficticio de su original y devuelve su valor de retorno, este valor de retorno puede usarse en la clase de prueba
Parte del objetivo de usar objetos simulados es que no tienen que implementarse realmente de acuerdo con las especificaciones. Ellos solo pueden dar respuestas falsas. Por ejemplo, si tiene que implementar los componentes A y B, y ambos "llamar" (interactuar) entre sí, entonces no puede probar A hasta que B se implemente, y viceversa. En el desarrollo basado en pruebas, este es un problema. Así que creas objetos simulados ("ficticios") para A y B, que son muy simples, pero dan algún tipo de respuesta cuando interactúan. De esta forma, puede implementar y probar A utilizando un objeto simulado para B.
Recomiendo encarecidamente un excelente artículo de Martin Fowler explicando qué son exactamente los simulacros y cómo se diferencian de los talones.
Un objeto falso es un tipo de prueba doble . Está utilizando objetos simulados para probar y verificar el protocolo / interacción de la clase bajo prueba con otras clases.
Por lo general, tipo de ''programa'' o ''graba'' expectativas: llamadas de método que espera que su clase 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 trata con la base de datos. Hablar con la base de datos es lento y configurarlo y limpiarlo después es complicado, así que nos burlaremos de WidgetDao.
pensemos qué debe hacer el servicio: debería obtener un Widget de la base de datos, hacer algo con él y guardarlo nuevamente.
Entonces, en un pseudo-lenguaje con una biblioteca pseudo-simulada tendríamos algo así 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 forma, podemos probar fácilmente el desarrollo de clases que dependen de otras clases.
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 forma controlada.
Un programador de computadoras típicamente 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 simulador de prueba de choque para simular el comportamiento dinámico de un ser humano en los impactos del vehículo.
http://en.wikipedia.org/wiki/Mock_object
Los objetos simulados le permiten configurar escenarios de prueba sin tener que soportar grandes recursos inmanejables, como bases de datos. En lugar de llamar a una base de datos para pruebas, puede simular su base de datos utilizando un objeto simulado en las pruebas de su unidad. Esto te libera de la carga de tener que configurar y destruir una base de datos real, solo para probar un solo método en tu clase.
La palabra "simulacro" a veces se usa erróneamente de manera intercambiable con "Stub". Las diferencias entre las dos palabras se describen aquí. Esencialmente, un simulacro es un objeto auxiliar que también incluye las expectativas (es decir, "afirmaciones") del 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());
}
}
Tenga en cuenta que los objetos simulados de warehouse
y mailer
están programados con los resultados esperados.