Desde que volví a trabajar en consultoría este es uno de los temas que más me he encontrado. Sinceramente no me sorprende pues:
- TDD y la automatización de pruebas, son dos temas que están en claro ascenso de popularidad
- Casi toda la bibliografía de TDD parte de la base de la creación de aplicaciones desde cero
- Pero en una porción importante de casos, la gente ya cuenta con una aplicación, la cual muchas veces no ha sido diseñada de forma de facilitar su prueba
Casualmente ayer me crucé con un caso extremo. Resulta que hace un par de semanas dicté un workshop de TDD. Ayer me contactó uno de los asistentes para hacer una consulta de como encarar un caso concreto: tenia que agregar una funcionalidad a la una clase existente. La clase en cuestión tenia más de 3000 líneas de código. Si si, leiste bien, son tres ceros después del tres, o sea, tres mil líneas de código. El problema de una clase tan grande, es que resulta dificil que sea cohesiva. Se supone que pasar ser cohesiva, una clase debe hacer UNA cosa y hacer bien. Con tantas líneas, es muy posible que dicha clase esté haciendo demasiadas cosas.
Pero el problema que motivaba la consulta no eran las 3000 líneas de código, sino el constructor de dicha clase. Resulta que el constructor además de recibir varios parámetros, instanciaba un componente para conectarse a la base de datos. Eso impedia realizar una prueba unitaria de la clase, pues el solo hecho de instancialar implicaba conectarse a la base. Tampoco no habia chances de mockear la conexión pues era instanciada directamente dentro del constructor. ¿que hacer entonces?
Lo primero que uno intentaria es hacer un refactoring de la clase, pero dado que no existian pruebas de la misma, cualquier modificación implicaba un gran riesgo. Luego de analizar varias alternativas llegamos a una solución de compromiso, que nos permitiria escribir tests unitarios: agregar un constructor sin parámetros, que tenga la lógica para lanzar un excepción si era invocado en un ambiente distinto al de desarrollo/test. De esta forma podriamos instancias la clase en forma aislada en un ambiente de test y aseguramos que no sea instanciada de esta forma en caso de estar en un ambiente distinto.
Y si hacías un factory que realice la conexión, externo, que sea llamado en el contructor… ¿pero dentro de este factory se ‘sepa’ qué hacer en cada caso? no entiendo mucho qué es lo que estás haciendo, pero me suena una solució ‘saludable’.
El tema es que eso implicaria modificar un método (el contructor) que no tiene pruebas, ups! Demasiado riesgoso.
Y si todo el código del constructor se mueve a un método de inicialización virtual??
De esa manera se podría mockear la clase y stubear el método de inicialización dejándolo vacio
Si, seria claramente una posibilidad.
Al mismo tiempo eso nos llevaria a otro debate conceptual: ese nuevo método debiera ser privado y los métodos privados no siempre pueden (stub/mock)earse.
Buen punto!!
Por otro lado si vemos al constructor de una clase como un método más de dicha clase no veo ningún conflicto en que el método de inicialización sea público. De todas maneras hay que ver qué tecnología estás usando, ya que eventualmente se puede «hacer trampa» y acceder al método aunque sea privado 😉