Hace algún tiempo leí, en una respuesta de desbordamiento de pila que no puedo encontrar, una oración que explicaba que debería probar las API públicas, y el autor dijo que debería probar las interfaces. El autor también explicó que si la implementación de un método cambiara, no debería necesitar modificar el caso de prueba, ya que esto rompería el contrato que asegura que el sistema bajo prueba funcione. En otras palabras, una prueba debería fallar si el método no funciona, pero no porque la implementación haya cambiado.
Esto llamó mi atención cuando hablamos de burlarse. Dado que la burla depende en gran medida de las expectativas de llamadas del sistema bajo las dependencias de prueba, las simulaciones están estrechamente relacionadas con la implementación en lugar de la interfaz.
Mientras investigan simulacros versus trozos, varios artículos coinciden en que se deben usar trozos en lugar de simulacros, ya que no se basan en las expectativas de las dependencias, lo que significa que la prueba no necesita conocer el sistema subyacente bajo la implementación de la prueba.
Mis preguntas serían:
- ¿Los simulacros violan el principio abierto / cerrado?
- ¿Hay algo que falta en el argumento a favor de los talones en el último párrafo, que hacen que los talones no sean tan buenos frente a los simulacros?
- Si es así, ¿cuándo sería un buen caso de uso para burlarse y cuándo sería un buen caso de uso para usar talones?
fuente
Since mocking relays heavily on expectation calls from system under test's dependencies...
Creo que aquí es donde vas mal. Un simulacro es una representación artificial de un sistema externo. No representa el sistema externo de ninguna manera, excepto en la medida en que simula el sistema externo de tal manera que permite ejecutar pruebas contra el código que depende de dicho sistema externo. Aún necesitará pruebas de integración para demostrar que su código funciona con el sistema real sin desmontar.Respuestas:
No veo por qué los simulacros violarían el principio abierto / cerrado. Si pudiera explicarnos por qué cree que podrían hacerlo, entonces podremos aliviar sus inquietudes.
La única desventaja de los stubs en los que puedo pensar es que generalmente requieren más trabajo para escribir que los simulacros, ya que cada uno de ellos es en realidad una implementación alternativa de una interfaz dependiente, por lo que generalmente debe proporcionar una completa (o convincentemente completa) implementación de la interfaz dependiente. Para darle un ejemplo extremo, si su subsistema bajo prueba invoca un RDBMS, entonces un simulacro de RDBMS simplemente respondería a consultas específicas que se sabe que son emitidas por el subsistema bajo prueba, produciendo conjuntos predeterminados de datos de prueba. Por otro lado, una implementación alternativa sería un RDBMS completo en memoria, posiblemente con la carga adicional de tener que emular las peculiaridades del RDBMS cliente-servidor real que está utilizando en la producción. (Afortunadamente, tenemos cosas como HSQLDB, por lo que podemos hacer eso, pero aún así,
Los buenos casos de uso para burlarse son cuando la interfaz dependiente es demasiado complicada para escribir una implementación alternativa para ella, o si está seguro de que solo escribirá el simulacro una vez y nunca lo volverá a tocar. En estos casos, siga adelante y use un simulacro rápido y sucio. En consecuencia, los buenos casos de uso para stubs (implementaciones alternativas) son prácticamente todo lo demás. Especialmente si prevé entablar una relación a largo plazo con el subsistema bajo prueba, definitivamente opte por una implementación alternativa que será agradable y limpia, y requerirá mantenimiento solo en caso de que la interfaz cambie, en lugar de requerir mantenimiento cada vez que la interfaz cambios y siempre que cambie la implementación del subsistema bajo prueba.
PD: La persona a la que te refieres podría haber sido yo, en una de mis otras respuestas relacionadas con las pruebas aquí en programmers.stackexchange.com, por ejemplo esta .
fuente
an alternative implementation would be a full-blown in-memory RDBMS
- No necesariamente tienes que ir tan lejos con un trozo.El principio Abierto / Cerrado se trata principalmente de poder cambiar el comportamiento de una clase sin modificarla. Por lo tanto, inyectar una dependencia de componente simulada dentro de una clase bajo prueba no la viola.
El problema con los dobles de prueba (simulacro / trozo) es que básicamente hace suposiciones arbitrarias con respecto a cómo la clase bajo prueba interactúa con su entorno. Si esas expectativas son incorrectas, es probable que tenga algunos problemas una vez que se implemente el código. Si puede permitírselo, pruebe su código dentro de las mismas restricciones que la que limita su entorno de producción. Si no puede, haga la menor cantidad de suposiciones posible y simule / bloquee solo los periféricos de su sistema (base de datos, servicio de autenticación, cliente HTTP, etc.).
La única razón válida por la cual, en mi humilde opinión, se debe usar un doble, es cuando necesita registrar sus interacciones con la clase bajo prueba, o cuando necesita proporcionar datos falsos (que ambas técnicas pueden hacer). Sin embargo, tenga cuidado, su abuso refleja un mal diseño o una prueba que se basa demasiado en la API bajo la implementación de la prueba.
fuente
Nota: Supongo que está definiendo Mock como "una clase sin implementación, solo algo que puede monitorear" y Stub como "simulacro parcial, también conocido como el comportamiento real de la clase implementada", según esta Pila Pregunta de desbordamiento .
No estoy seguro de por qué crees que el consenso es usar trozos, por ejemplo, es todo lo contrario en la documentación de Mockito
Esa documentación lo dice mejor que yo. El uso de simulacros le permite probar esa clase en particular y nada más; Si necesita simulacros parciales para lograr el comportamiento que está buscando, probablemente haya hecho algo mal, esté violando SRP, etc., y su código podría ser un refactorizador. Los simulacros no violan el principio abierto-cerrado, ya que de todos modos solo se usan en pruebas, no son cambios reales en ese código. Por lo general, se generan sobre la marcha de todos modos por una biblioteca como cglib.
fuente
Creo que el problema puede surgir de la suposición de que las únicas pruebas válidas son aquellas que cumplen con la prueba abierta / cerrada.
Es fácil ver que la única prueba que debería importar es la que prueba la interfaz. Sin embargo, en realidad, a menudo es más efectivo probar esa interfaz probando el funcionamiento interno.
Por ejemplo, es casi imposible probar cualquier requisito negativo, como "la implementación no arrojará ninguna excepción". Considere la implementación de una interfaz de mapa con un hashmap. Desea estar seguro de que el hashmap se encuentra con la interfaz del mapa, sin tirar, incluso cuando tiene que volver a mostrar cosas (lo que podría volverse incierto). Puede probar cada combinación de entradas para asegurarse de que cumplan con los requisitos de la interfaz, pero eso podría llevar más tiempo que la muerte por calor del universo. En cambio, rompe un poco la encapsulación y desarrolla simulacros que interactúan más estrechamente, obligando al hashmap a hacer exactamente la repetición necesaria para garantizar que el algoritmo de repetición no se ejecute.
Tl / Dr: hacerlo "por el libro" es bueno, pero cuando se trata de empujar, tener un producto en el escritorio de su jefe antes del viernes es más útil que una suite de prueba por el libro que lleva hasta la muerte por calor del universo para confirmar la conformidad.
fuente