S.O.L.I.D. Principles, los necesitamos para testear bien (Parte 2/2)

El viernes pasado hablábamos de los Principios S.O.L.I.D se explicaban los primeros, se mostraron ejemplos y se relacionaban con el testing (puedes leer el post completo aquí). Hoy queremos seguir contándote el resto de los principios S.O.L.I.D, asi que vamos a ello…

Liskov Substitution Principle (LSP) – Principio de Sustitución de Liskov

En lenguajes OO (Orientados a Objetos) como C# o VB.NET, la clave para conseguir la abstraccion y polimorfismo de entidades es mediante la herencia, y es precisamente en esta característica en la que se basa el Principio de Sustitución de Liskov (Liskov Substitution Principle, LSP).
Liskov explicaba que si tenemos un objeto “o1” de tipo S y un objeto “o2” de tipo T el comportamiento de dichas funciones no cambia cuando se sustituye “o1” por “o2” porque S es un subtipo de T. Es decir, las funciones que tienen referencias a clases principales tienen que ser capaces de utilizar objetos de clases cuyo tipo sea subtipo de la clase base.
Resumido en una frase, los subtipos deberían poder ser reemplazables por sus tipos base.
Cada clase que hereda de otra puede usarse como su padre sin necesidad de conocer las diferencias entre ellas, esto nos obliga asegurarnos que cuando extendemos una clase no alteramos el comportamiento de la clase padre.
Respecto al Testing es importante porque si tenemos pruebas unitarias para la clase padre también funcionarán para la subclase, de esta forma se hace uso de la reutilización y se disminuye el esfuerzo empleado en el Testing.
Un problema habitual es que la mayoría de los lenguajes no proporcionan un mecanismo explícito para definir la semántica de un método. A lo sumo se pueden definir las características del método, es decir, nombre, los tipos de los parámetros, el tipo del retorno, etc.
Entonces…  ¿Cómo podemos ayudar a los desarrolladores de nuevas subclases a asegurarse de que sus clases se comporten correctamente, satisfaciendo el principio de sustitución?
Para solventar en la mayor medida este problema, afortunadamente tenemos una metodología que nos permite definir cómo una clase debe comportarse incluso antes de escribir esta clase: Desarrollo Impulsado por Pruebas (TDD).
De acuerdo con el enfoque TDD, un desarrollador debe escribir pruebas unitarias que comprueben la correcta implementación de todos los aspectos funcionales importantes de una clase. Es común tener múltiples pruebas para cada método, verificando qué sucede cuando hay diferentes parámetros o cuando el objeto que se está probando tiene un estado diferente.
Si ya tenemos un conjunto de pruebas unitarias que comprueban el comportamiento de alguna clase, simplemente podemos aplicar las mismas pruebas (pruebas de regresión) en la nueva subclase que hemos creado y observar si hay algo roto.
Cuando ejecutamos las pruebas unitarias reemplazando una instancia de esta clase por una instancia de la nueva subclase, en realidad estamos verificando si la subclase cumple con el Principio de Sustitución.
Para este principio también queremos dejaros un ejemplo mediante dibujos. En la siguiente imagen se puede ver que el método “Dibujar” viola este principio.

Violación del principio LSP.
Violación del principio LSP.

En la imagen anterior, el método Dibujar viola el principio de Liskov, porque el método Dibujar no conoce más allá del Polígono y del Círculo, sin embargo tiene que ser conocer cualquier subtipo de Figura que creemos en nuestro sistema. Además también se viola el principio abierto/cerrado.
La solución a esto será usar polimorfismo. Cada tipo de Figura será responsable de implementar el método dibujar.

Interface Segregation Principle (LIP) – Principio de Segregación de Interfaces

Siguiendo con los principios S.O.L.I.D, el Principio de Segregación de Interfaces fue utilizado por primera vez por Robert C. Martin durante unas sesiones de consultoría en Xerox.
El Principio de Segregación de Interfaces define que una clase no debe ser forzada a depender de interfaces que no usa. «Los clientes no deberían estar obligados a depender de interfaces que no utilicen». — Robert Martin, paper sobre LIP enlazado desde Los Principios del Diseño Orientado a Objetos.
El crear interfaces grandes obliga a desarrollar implementaciones muy extensas, por ello este principio se trata de mantener las interfaces y clases abstractas lo más pequeña posibles y limitadas a una responsabilidad o necesidad.
El LIP fue concebido para mantener a un sistema desacoplado respecto a los sistemas de los que depende, y así resulte más fácil refactorizarlo, modificarlo y redesplegarlo.
Mediante la segregación de interfaces, el planteamiento del diseño da una mayor cohesión al sistema, lo que se traduce, por una parte, en un menor coste de mantenimiento, y por otra, en un menor riesgo de errores.
Por tanto, cuando creemos interfaces que definan comportamientos, es importante estar seguros de que todas las clases que implementen esas interfaces se vayan a necesitar. En caso contrario, es mejor tener varias interfaces más pequeñas.
Respecto al testing este principio también ayuda a las pruebas unitarias, y su mantenimiento.

Ejemplo sin usar LIP
Ejemplo sin usar LIP

Una buena forma de haber refactorizado o modelado el diagrama anterior es dividir la interfaz transporte en otras más pequeñas.
División de una interfaz en otras más pequeñas usando LIP
División de una interfaz en otras más pequeñas usando LIP

Dependency Inversion Principle (DIP) – Principio de Inversión de Dependencias

El principio DIP dice que si una clase depende de otras clases, esta relación debería ser de dependencia de interfaces en lugar de dependencia de implementaciones concretas. Este principio defiende la idea que si se producen cambios en los detalles de las abstracciones, no se produzcan en las clases, para que el diseño sea más fácil de cambiar.

  1. Los módulos de alto nivel no deberían depender de módulos de bajo nivel. Ambos deben depender de abstracciones.
  2. Las abstracciones no deben depender de detalles. Los detalles deben depender de abstracciones.» — Robert Martin, paper sobre DIP enlazado desde Los Principios del Diseño Orientado a Objetos.

Se pretende reducir el acoplamiento entre los componentes del software todo lo que se pueda con el uso de abstracciones, así una clase interactúa con otra sin que se conozcan directamente. Además los detalles del código se especificarán mediante interfaces.
La inversión de dependencias se puede realizar siempre que una clase envía mensaje a otra y deseamos eliminar la dependencia.
Os mostramos un ejemplo que pone el Tío Bob para explicar este principio. El botón depende de la lámpara ya que esta es la que se enciende y apaga.

Diseño que no cumple el principio de independencias
Diseño que no cumple el principio de independencias

Sin embargo si utilizamos la abstracción eliminamos esta dependencia, ya que ahora el botón se podrá utilizar con cualquier cosa que se pueda encender o apagar por ejemplo un motor.
Diseño que cumple el principio de independencias
Diseño que cumple el principio de independencias

Si utilizamos la inyección de dependencias que nos permite tener poco acoplamiento, por lo que son más independientes y respecto al testing va a ser más fácil utilizar TDD. Pero hay que tener cuidado porque un sistema puede utilizar la inyección de dependencias y no seguir el principio DIP por lo que tenemos que estar seguros de que sigue el principio.

Terminando…

En estos dos post, os hemos querido dejar un resumen, una pequeña introducción a cada uno de los principios S.O.L.I.D. Más adelante, poco a poco, nos iremos centrando en cada uno de ellos y con ejemplos prácticos, que seguramente se entienda mucho mejor que con la teoría.
Esperamos que sea de vuestro interés y que sigáis profundizando en el tema, ya que como hemos visto, es muy importante cumplir estos principios para poder testear bien.
 

jgarzas

Ph.D. en informática, Postdoctorado en la Carnegie Mellon (EE.UU) e Ingeniero en Informática.

Primera vez que me tocó hacer una gestión Ágil en una empresa... año 2001. Desde entonces he trabajado en, o para, más de 90. Y he formado a más de 2000 alumnos.

También soy profe de la Universidad Rey Juan Carlos.

2 comentarios en “S.O.L.I.D. Principles, los necesitamos para testear bien (Parte 2/2)”

  1. Hola,
    Si revisándolo, la caja de autobús hay q intercambiarla por la de avión, para que avión se conecte con las interfaces voladora y transporte y autobús conecte con las interfaces transporte y corredor
    Muchas gracias por la observación cualquier otra duda nos comentas
    Un saludo

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *