¿Los simulacros violan el principio Abierto / Cerrado?

13

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:

  1. ¿Los simulacros violan el principio abierto / cerrado?
  2. ¿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?
  3. 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?
Christopher Francisco
fuente
8
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.
Robert Harvey
8
Para decirlo de otra manera, el simulacro es una implementación sustituta. Por eso, en primer lugar, programamos una interfaz para poder utilizar simulacros como sustituto para la implementación real. En otras palabras, los simulacros se desacoplan, no se acoplan a, la implementación real.
Robert Harvey
3
"En otras palabras, una prueba debería fallar si el método no funciona, pero no porque la implementación haya cambiado", esto no siempre es cierto. Hay muchas circunstancias en las que debe cambiar tanto su implementación como sus pruebas.
whatsisname

Respuestas:

4
  1. 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.

  2. 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í,

  3. 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 .

Mike Nakis
fuente
an alternative implementation would be a full-blown in-memory RDBMS- No necesariamente tienes que ir tan lejos con un trozo.
Robert Harvey
@RobertHarvey, con HSQLDB y H2 no es tan difícil llegar tan lejos. Probablemente sea más difícil hacer algo a medias para no llegar tan lejos. Pero si decide hacerlo por su cuenta, tendrá que comenzar escribiendo un analizador SQL. Claro, puedes cortar algunas esquinas, pero hay mucho trabajo . De todos modos, como dije anteriormente, este es solo un ejemplo extremo.
Mike Nakis
9
  1. 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.

  2. 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.

Francis Toth
fuente
6

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

Como de costumbre, va a leer la advertencia de simulación parcial: la programación orientada a objetos es más menos abordar la complejidad al dividir la complejidad en objetos SRPy separados y específicos. ¿Cómo encaja la simulación parcial en este paradigma? Bueno, simplemente no ... El simulacro parcial generalmente significa que la complejidad se ha movido a un método diferente en el mismo objeto. En la mayoría de los casos, esta no es la forma en que desea diseñar su aplicación.

Sin embargo, hay casos raros en los que los simulacros parciales son útiles: tratar con código que no puede cambiar fácilmente (interfaces de terceros, refactorización provisional de código heredado, etc.) Sin embargo, no usaría simulacros parciales para nuevos, basados ​​en pruebas y bien código diseñado

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.

durron597
fuente
2
De la misma pregunta SO proporcionada (respuesta aceptada), esta es la definición Mock / Stub a la que me refería también: los objetos Mock se usan para definir expectativas, es decir: en este escenario, espero que el método A () se llame con tales o cuales parámetros. Las simulaciones registran y verifican tales expectativas. Los trozos, por otro lado, tienen un propósito diferente: no registran ni verifican las expectativas, sino que nos permiten "reemplazar" el comportamiento, el estado del objeto "falso" para utilizar un escenario de prueba ...
Christopher Francisco
2

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.

Cort Ammon
fuente