¿Vale la pena crear pruebas unitarias?

La creación de pruebas unitarias requiere al menos lo siguiente:

  • Un framework de pruebas unitarias que debemos dominar. Por lo general son muy simples.
  • Código testeable. Típicamente esto implica código susceptible de ser “aislado”.

Nada que decir con respecto al primer punto. Ahora, en cuanto al segundo punto, ¿qué significa que el código pueda ser “aislado”?. Bueno, esto significa que sus dependencias deben poder ser reemplazadas. Esto lo logramos bien por medio de inyectarle sus dependencias o bien utilizando un patrón ServiceLocator (a algún framework con un contenedor de dependencias).

Entonces, una vez resuelto, necesitamos proveer al código objeto de prueba, dependencias “falsas”. Esto nuevamente requiere o bien que las creemos a manos o que  utilicemos algún isolation framework para crearlas (por lo general en runtime).

Hasta ahora nada que no sepamos todos ¿verdad? Pero… ¡Vamos de nuevo! Veámoslo en código por favor. Imaginemos que tenemos el siguiente método pero no podemos probarlo porque su dependencia con el LogHelper (una clase con métodos estáticos) nos lo impiden:

public class TransferManager

{

    public void Tranfer(Account from, Account to, Money amount)

    {

        // Perform the required actions

        LogHelper.Inform("Done!");

    }

}

Si no quisiéramos enredarnos mucho, lo que haríamos sería algo como lo que sigue:

public class TransferManager

{

    private readonly ILogger _logger;

 

    public TransferManager(ILogger logger)

    {

        this._logger = logger;

    }

 

    public void Tranfer(Account from, Account to, Money amount)

    {

        // Perform the required actions

        _logger.Inform("Done!");

    }

}

Y la prueba, se parecería a lo que sigue:

[TestMethod]

public void Perform_a_valid_tranfer()

{

    var aFakeLogger = new FakeLogger();

    var tranferManager = new TransferManager(aFakeLogger);

 

    // Do and Assert here

}

Lo que falta:

interface ILogger

{

    void Inform(string messageToLog);

}

 

class FakeLogger : ILogger

{

    public void  Inform(string messageToLog)

    {

        // Nothing to do

    }

}

¿Que debimos hacer para probar esa pieza de código? Bueno, varias cosas: en primer lugar fue necesario crear una interface hasta el momento innecesaria (ILogger), tuvimos que agregar un constructor para inyectarle esta dependencia juntamente con un campo para almacenarla; debimos además, y aunque no se muestre aquí, hacer un wrapper para el LogHelper que implemente la interface ILogger. Por otro lado, en el proyecto de prueba, debimos crear una clase falsa (la FakeLogger).

Creamos FakeLogger de manera manual y con su implementación más sencilla (no hace absolutamente nada) pero bien podríamos haberla creado con un framework de aislamiento y especificarle algún comportamiento.

Es decir que el código es ahora más complejo que antes ya que tiene, al menos en este ejemplo, un campo más (_logger), un constructor más, una interface más (la ILogger) y una clase más (el wrapper del LogHelper). El código que crea una instancia de esta clase (TranferManager) también se ve afectado. Este es el costo que lleva asociado ineludiblemente el probar, mediante pruebas unitarias, cualquier código.

Claro, la pregunta es: ¿Valdrá la pena?

Ruidos y señales en nuestra industria

Voy a tomar prestado las palabras señal y ruido del campo de las telecomunicaciones para explicar una situación que nos afecta de cerca. Entendamos la palabra señal, en el contexto de esta entrada, como el mensaje, lo importante, lo esencial y como ruido lo indeseable, lo molesto, lo que encarece y entorpece al mensaje.

La señal, o el mensaje en este caso, que deseamos transmitir, por lo general todos los que hemos estado en esto durante algún tiempo (y que nos hemos equivocado terriblemente una infinidad de veces y lo vamos a seguir haciendo), es que educación (léase como entrenamiento o capacitación), trabajo en equipo, orden (o método), comunicación, simplicidad,  motivación, experiencia, más experiencia, buen management y buenas prácticas son los puntos claves para el éxito en la empresa de desarrollar software.

El ruido, por otro lado, lo constituyen los maremotos de frameworks que se ponen de moda y cuya vigencia no supera el año o dos, las tendencias y sus variantes (TDD y BDD) y sus productos forzados (Isolation Frameworks, IoC containers/DI) que a su vez son más frameworks o requieren agregar complejidad al código, Patrones y más patrones (alguno de ellos requeridos, utilizados, o introducidos por algunas prácticas o frameworks), nuevos lenguajes con ideas antiquísimas (funcionales y dinámicos), otros lenguajes resucitados (Lisp) y sus mil variantes o dialectos (clojure, scheme y otros), application blocks, ORMs y la lista puede seguir ad infinitum.

Podríamos discutir un año completo el por qué ninguna de estas cosas incrementa el éxito de los proyectos de desarrollo en una forma sustancial. Pero como evidencia podemos recurrir a la realidad cotidiana. ¡Cuidado! No estoy diciendo que todos los frameworks son puro ruido, no estoy diciendo que algunos lenguajes no introducen mejoras, no afirmo que la inyección de dependencia no tiene excelentes usos ni que los patrones, que nos han elevado al grado de que hoy somos más diseñadores que programadores, sean inútiles; NO. No nada de eso, lo que digo es que su contribución al éxito de los proyectos (léase si se quiere como: costos, tiempo, presupuesto y calidad) y en lo que entregamos a los clientes es sencillamente marginal.

En software como en telecomunicaciones, el ruido puede transportar señal también. Esto quiere decir que de todo eso que hoy está de moda, pero que mañana solo servirá para reírnos un rato, algo queda y mucho se aprende.

Mi preocupación va por el lado de la desproporción en la relación señal/ruido. Creo que la señal es débil mientras que el ruido es muy fuerte. Muchas veces por la necesidad de vender productos las compañías introducen gran parte del ruido. Otras veces, somos nosotros cuando resaltamos el ruido cuando buscamos gente “Perfil: deberá conocer WCF, Mockito (conocimiento de Moles se tomará en cuenta), Prism, MVVM, Unity, Linq, JQuery y controles de Tellerik”. Esto amplifica el ruido tremendamente. Al cliente por otra parte seguramente le entregaremos una solución bastante parecida a lo que le hubiésemos entregado hace 3 años cuando ningunas de esas cosas existía.

Horas extras y las cadenas psíquicas

Siempre veo y escucho de gente que trabaja 2, 3, 4 y hasta 5 horas de más, personas que deberían salir de sus lugares de trabajo a las 18hs se quedan hasta las 22hs o 23hs sin ninguna compensación. Es cierto que algunas veces uno debe terminar un trabajo y ello puede requerir quedarse algunas horas extras por unos días. No hay nada malo en ello, el problema surge cuando esa situación no se da de manera excepcional sino que es una constante, cuando las personas se quedan a hacer sobre turnos por semanas o incluso meses.

Por lo general, las respuestas que dan aquellos que así lo hacen cuando se les pregunta el por qué de quedarse hasta esas horas son algunas de estas:

  • porque el proyecto lo necesitaba,
  • porque ellos se comprometieron a tenerlo en esa fecha y/o,
  • porque ellos son muy responsables.

Seguro podríamos discutir estas respuestas desde algunos puntos de vista técnicos-metodológicos-ingenieriles pero esta vez no. Esta vez quiero analizarlo desde el punto de vista psicológico. Para esto, partamos de la base de que las personas saben que afuera hay un mundo, que tienen una novia, una esposa, unos hijos que los esperan. Se dan cuenta que, al menos en invierno y en mi provincia, entran a trabajar de noche y salen de trabajar de noche. Todo el mundo es consciente de que su tiempo es finito y que la vida se les pasa pero aún así se quedan a trabajar muchas más horas. ¿A cambio de qué? De nada.

¿Cómo es posible entonces? La verdad es que esa declamada responsabilidad es solo una excusa inconsciente, una trampa psicológica que los protege de la angustia de saber que están tirando su vida al tacho. Esto nos lleva a otra pregunta: ¿qué les impide irse a casa a las 18hs? En apariencia, nada se los impide. Es más, hasta se les podría culpar de ser adictos al trabajo. Pero si se necesita de una excusa inconsciente para justificarse por hacer algo es porque existen otras fuerzas invisibles que les impiden irse a las 18hs.

La primera y quizás más poderosa es la fuerza del grupo. Una vez que un grupo acepta trabajar más horas que aquellas inicialmente pactadas, todo nuevo integrante se someterá al comportamiento e idiosincrasia del grupo. El experimento de conformidad de Asch es una muestra clara y por demás concluyente de que la individualidad se distiende en gran medida para concordar con la visión grupo. Si todos trabajan hasta las 20hs, aún cuando no fuese necesario, aquel que se fuera a las 18hs sufriría una presión enorme. Ver el experimento del ascensor.

La segunda es el de la autoridad. El poder que la autoridad tiene para suprimir la voluntad del individuo llega a límites insospechados. El experimento de obediencia a la autoridad de  Milgram, el que se recreó muchísimas veces con similares conclusiones, prueba que en el 65% de los casos, personas comunes llegan a aplicar descargas de 450 voltios a un semejante solo por obediencia, esto en algunos casos equivale a matarlos. Entonces, en las organizaciones, donde la estructura de autoridad es absolutamente vertical y se encuentra claramente establecida, la obediencia es un factor clave. Aunque las frases: “Quiero esto para mañana” y “Hasta que no esté resuelto no se va nadie de acá” son ejemplos crudos y explícitos, las ordenes no tienen por qué ser tan explicitas (la sugestión es mucho más efectiva incluso) para que sean acatadas cuando quien las imparte ha sido embestido con autoridad por la compañía. Por otro lado, la obediencia en las organizaciones se muestra más como un afán por mostrarse cooperativo que como el cumplimiento de una orden.

Son estas dos fuerzas las que, junto con otras como la de “comprometerse” (quizás con las estimaciones dichas en público), crean un malestar psíquico como producto de la disonancia entre lo que hacen, de manera forzada, y lo que realmente piensan sobre lo que hacen. La magnitud del malestar es proporcional a la magnitud de la disonancia y por ello, para disminuir la angustia, buscan mediante una excusa, estar en mayor consonancia con lo que hacen. Ver “Cognitive consequences of forced compliance

El problema detrás de todo esto es que el malestar tiene que desaparecer de alguna manera. Por lo general, las personas que hacen sobre turnos están dispuestos a cambiar de trabajo mucho más fácilmente que aquellos que no los hacen. Es un problema real para las organizaciones que tiene costos asociados los cuales son muy difíciles de medir. ¿Cual es la ganancia asociada a una persona que trabaja durante 3 meses algo más de 2 horas extras para luego irse a la competencia? Nadie lo sabe.

Pero lo más triste es que muchas de estas personas (colegas) que se cambian a la competencia en busca de una realidad distinta, terminan en exactamente las mismas situaciones solo que en una empresa diferente. Siempre que hablo con alguien que quiere “escapar” a otra empresa para cambiar su realidad actual le pregunto: ¿Y qué vas a hacer para que en la nueva empresa no te termine pasando lo mismo? Nadie lo sabe.

Yo tampoco tengo la respuesta pero sin dudas parte de culpa pertenece a las personas que aceptan esos sobre turnos. Aún cuando puedan irse autoimponiendo de manera gradual y progresiva.

Soluciones creativas

Esto me plantearon en mi última entrevista de trabajo:

Entrevistador: Imaginate que tenés una grilla que lista todos los mp3 que tiene un usuario en su computadora. Esa grilla puede ordenar las canciones de manera ascendente o descendente simplemente presionando sobre la cabecera de la columna por la que el usuario quiera ordenarlas. El problema es que el usuario observa un pequeño, pero molesto ´delay’ en el refresco de la grilla. ¿Cómo lo resolverías?

Era evidente que me estaban preguntando por un caso real que ellos ya habían ‘resuelto’, simplemente ordenando la lista por todos los criterios de antemano. Ahí supe que la entrevista no iba a terminar bien, y así fue.

Yo: Si el delay en el refresco es de, digamos, medio segundo y la grilla tiene 10 columnas (nombre de la canción, autor, álbun, año, género, duración, pista, nombre de archivo, path y calificación), el tiempo necesario para ordenar toda la lista por los 10 criterios sería de aproximadamente 5 segundos!!!. Nadie puede esperar 5 segundos. El tiempo de procesamiento para ordenar la lista no puede hacerse desaparecer, solo se puede trasladar a otro momento. ¿Estará de acuerdo el cliente con cambiar delays de medio segundo por un solo pago al contado de 5 segundos al cargar la lista?

Entrevistador: Sí, tarda un poquito al cargar la lista y por eso es que a la ordenación la vamos a hacer en paralelo, un hilo por columna.

Yo: ¿Y qué sucede si el usuario quiere presiona sobre la cabecera de una columna por la que la lista no está ordenada todavía?

Entrevistador: No creo que un usuario pueda ser tan rápido! (risas). Además, de suceder eso, existen maneras de darle prioridad a los hilos para que ordenen primero las columnas más usadas.

Yo: ummm…. ¿y no te parece que le agrega una complejidad innecesaria?. ¿Qué pasa si la lista es muy grande y el usuario si es Tan Rápido? o ¿Cómo saber cuáles son las columnas preferidas de cada usuario?

Entrevistador: ehhh… bueno, las listas no van a ser grandes ade….

Yo: (interrumpo) ¿En cuánto estimaron el costo de esa ‘optimización’’? ¿Cuántos pesos, dólares, euros o, cual sea la moneda, le cuesta al cliente? ¿Está de acuerdo?

Entrevistador: No, no lo estimamos porque el cliente lo quiere si o si. Nosotros buscamos profesionales que puedan encontrar soluciones creativas a problemas como este pero veo que vos le encontrás problemas creativos a cada solución que te planteo. Vos, ¿qué harías?

Yo: por el momento no haría nada. Medio segundo es solo un parpadeo. Lo conversaría con el cliente nuevamente.

Entrevistador: bueno Lucas, nos vamos a estar contactando con vos. Gracias.

No me llamaron así que no sé cómo termina la historia aunque puedo imaginármela: varias líneas de código extras, un par de hilos, varios bugs con los que va a ser difícil lidiar, mayor complejidad y menor mantenibilidad, mayores costos, tiempos y un cliente posiblemente desconforme. Mucha suerte muchachos!