¿Por qué las pruebas unitarias son más difíciles en la programación orientada a objetos en comparación con la programación funcional?

9

Estoy pasando por esta serie . El autor menciona que, dado que el estado se mantiene en la programación orientada a objetos, es más difícil escribir pruebas unitarias. También dice que, dado que la programación funcional no mantiene el estado (no siempre), es más fácil escribir pruebas unitarias. No vi ningún ejemplo que demuestre este problema. Si esto es cierto, ¿podría darme un ejemplo comparando pruebas unitarias en programación funcional y orientada a objetos?

0112
fuente
2
No estoy de acuerdo con la idea de que la programación funcional es más fácil de probar. Si bien es cierto que la función sin estado es más fácil de probar que el objeto con estado, la conversión de un objeto con estado en función sin estado da como resultado una función de estado mucho más compleja, posiblemente con alguna emulación de estado compleja, como las mónadas.
Eufórico
1
@Euphoric Es probable que una interpretación funcional de una solución a un problema sea bastante diferente a simplemente reemplazar el estado con una Statemónada (que emula el estado). Parcialmente tiene un punto: el código escrito en ese estilo tiene interacciones no locales, pero a los efectos de la prueba unitaria, todavía puede pasar explícitamente en el estado, no tiene que burlarse de nada. Por otro lado, con mónadas como STy IO(que tienen un estado mutable verdadero e implícito), simplemente está haciendo una programación con estado, y las pruebas unitarias no serán más fáciles que en cualquier otro lenguaje.
Derek Elkins dejó SE
10
No hay nada en la definición de OO que requiera un estado mutable. Es perfectamente posible (y ampliamente hecho) escribir código OO puro y transparente referencialmente.
Jörg W Mittag
1
Todas las técnicas asociadas con la programación funcional son perfectamente aplicables en la programación orientada a objetos. Los programas OO bien diseñados suelen utilizar técnicas funcionales en los niveles inferiores, como mínimo.
Frank Hileman
1
@Fabio Si ese es el caso, tiene más sentido usar la programación FP. OO no requiere un estado mutable, pero la mutabilidad está implícita en el ejemplo que acabo de darle. Lo mismo ocurre con f #: puede marcar una variable mutable para adaptarse a las necesidades. Pero si va a usar demasiadas variables mutables, también podría usar c #.

Respuestas:

17

Quiero distinguir entre dos formas diferentes de abordar la programación orientada a objetos

  1. Simulacionista: sus objetos representan objetos de dominio real, los ha programado para manejar cualquier funcionalidad relacionada con ese dominio. Es probable que los objetos programados de esta manera tengan muchos estados mutables y colaboradores ocultos para implementar esta funcionalidad.
  2. Registros + funciones: sus objetos son solo paquetes de datos y funciones que operan sobre esos datos. Los objetos programados de esta manera son más aptos para ser inmutables, asumir menos responsabilidades y permitir inyectar colaboradores.

Una regla general es que un objeto programado en la primera forma tendrá más métodos y más voidmétodos que en la segunda forma. Digamos que íbamos a escribir un simulador de vuelo y estábamos diseñando una clase de avión. Tendríamos algo como:

class Plane {
    void accelerate();
    void deccelerate();
    void toggleRightFlaps();
    void toggleLeftFlaps();
    void turnRudderRight();
    void turnRudderLeft();
    void deployLandingGear();
    void liftLandingGear();
    // etc.
    void tick() throws PlaneCrashedException;
}

Esto es quizás un poco más extremo de lo que uno podría encontrar, pero hace que se entienda. Si desea implementar este tipo de interfaz, debe mantenerse dentro del objeto:

  1. Toda la información sobre el estado del equipamiento del avión.
  2. Toda la información sobre la velocidad / aceleración del avión.
  3. La frecuencia de actualización de nuestra simulación (para implementar tick).
  4. Detalles completos sobre el modelo 3D de la simulación y su física para implementar la marca.

Escribir una prueba unitaria para un objeto escrito en el modo es muy difícil porque:

  1. Debe proporcionar todos los diferentes bits de datos y colaboradores que este objeto necesita al comienzo de su prueba (instaurarlos puede ser realmente tedioso).
  2. Cuando desea probar un método, se encuentra con dos problemas: a) la interfaz con frecuencia no expone suficientes datos para que los pruebe (por lo que termina teniendo que usar simulacros / reflexión para verificar incluso las expectativas) b) hay muchos componentes vinculados en uno que debe verificar se comportó en cada prueba.

Básicamente, comienzas con una interfaz que parece razonable y que se adapta bien al dominio, pero la simulación de la simulación te ha engañado para crear un objeto que es realmente difícil de probar.

Sin embargo, puede crear objetos que cumplirían el mismo propósito. Querrías Planedividirlo en pedazos más pequeños. Tenga un PlaneParticledispositivo que rastree los bits físicos del avión, la posición, la velocidad, la aceleración, el balanceo, la guiñada, etc., etc., exponiendo y permitiendo que uno los manipule. Entonces un PlanePartsobjeto podría rastrear el estado de. Se podría enviar tick()a un lugar completamente diferente, por ejemplo, tienen un PlanePhysicsobjeto parametrizado por, por ejemplo, la fuerza de la gravedad, que sabe dar una PlaneParticley una PlanePartsforma de escupir una nueva PlaneParticle. Todo esto podría ser completamente inmutable, aunque no es necesario que sea por algún ejemplo.

Ahora tiene estas ventajas en términos de pruebas:

  1. Cada componente individual tiene menos que hacer y es más fácil de configurar.
  2. Puede probar sus componentes de forma aislada.
  3. Estos objetos pueden salirse al exponer sus elementos internos (especialmente si se hacen inmutables), por lo que no se necesita inteligencia para medirlos.

Aquí está el truco: el segundo enfoque orientado a objetos que describí está muy cerca de la programación funcional. Tal vez en los programas funcionales puros sus registros y sus funciones estén separados y no unidos en objetos, definitivamente un programa funcional garantizaría que todas estas cosas. Lo que creo que realmente facilita las pruebas unitarias es

  1. Unidades pequeñas (pequeño espacio de estado).
  2. Funciones con entrada mínima (sin entradas ocultas).
  3. Funciones con salida mínima.

La programación funcional fomenta estas cosas (pero uno puede escribir programas malos en cualquier paradigma), pero se pueden lograr en programas orientados a objetos. Y destacaría que la programación funcional y la programación orientada a objetos no son incompatibles.

Walpen
fuente
2
No estoy seguro de que sea una buena distinción. Incluso cuando se utiliza el enfoque 1, muchos considerarían las técnicas que describe para el enfoque 2 como una práctica estándar. Los dos enfoques no son mutuamente excluyentes. Los bits del Plano se pueden componer juntos para crear una abstracción más grande, y ahora se encuentra en el área del enfoque 1. Diría que el enfoque 1, como lo describe, no es lógico.
Frank Hileman
3
"Y destacaría que la programación funcional y la programación orientada a objetos no son incompatibles": algunos dirían que la programación orientada a objetos se caracteriza solo por un despacho dinámico. Por lo tanto, puede escribir en estilo imperativo OOP: objetos mutables + despacho dinámico, o en estilo OOP funcional : objetos inmutables + despacho dinámico.
Giorgio
3
Romper 1 objeto grande en partes más pequeñas no es una programación funcional. Y la solución a un gran objeto es no separar la lógica de los datos y hacerla anémica, lo que es funcional . Sin embargo, podría estar malinterpretando toda su parte.
Chris Wohlert