Introducción a los tests de unidad

Tengo ganas de “evangelizar” sobre buenas prácticas de desarrollo, así que durante los próximos días publicaré una serie de artículos sobre tests de unidad. Aquí va la introducción; los demás artículos serán más técnicos. Espero que la serie os parezca interesante :)


Los tests de unidad (“unit tests” en inglés) son programitas que ponen a prueba una unidad de un programa. Una unidad puede ser una función, una clase, o incluso un módulo entero. Estos programas ejecutan diversas partes de esta unidad con diversos parámetros de entrada, estados internos, etc., y comprueban que ésta produce los resultados correctos.

Los tests de unidad se deberían escribir junto con el código al que prueban, y se deben mantener actualizados de forma que siempre pasen con éxito (ya sea arreglando los fallos que se puedan introducir en la unidad que se prueba, o actualizando el test si el funcionamiento de la unidad ha cambiado). En algunos sitios incluso escriben los tests antes de escribir el código. Lo que hacen es codificar los requisitos en los tests, y así, cuando todos los tests pasan con éxito, saben que el módulo cumple todos los requisitos y además funciona bien.

Podría parecer a simple vista que mantener los tests de unidad al mismo tiempo que el código “de verdad” cuesta más trabajo que, simplemente, no tener tests de unidad. Sin embargo, los tests de unidad proporcionan varias ventajas que compensan con creces su existencia.

La principal es la mayor velocidad de desarrollo. Cuando hacéis un cambio en un programa y no tenéis tests de unidad, la única forma de probarlo consiste en compilar el programa, ejecutarlo, ir hasta la parte que habéis cambiado, hacer la prueba, etc. Sin embargo, con tests de unidad basta con compilarlos y ejecutarlos, y en menos de un minuto tenéis el resultado.

Además, los tests de unidad evitarán que introduzcáis muchos errores, ya que los tests no pasarán con éxito hasta que la unidad a prueba funcione razonablemente bien. Y, si en el futuro descubrís algún error, sólo tenéis que añadir un test que capture ese error, arreglarlo para que el test pase con éxito, y sabréis que no volveréis a introducir ese error en el futuro.

Otra ventaja es que con los tests de unidad podéis estar seguros de que vuestros cambios no tendrán efectos imprevistos. Por ejemplo, algo que se hace a menudo es cambiar la implementación de un módulo sin modificar su interfaz. Si tenéis tests de unidad e introducís algún error en la nueva implementación, alguno de estos tests fallará; cuando todos los tests pasen con éxito, podéis estar razonablemente seguros de que la nueva implementación es correcta. Sin tests de unidad no podéis estar tan seguros.

Otra ventaja importante es que los tests forman parte de la documentación del software. Los Javadoc tienen la molesta costumbre de quedar obsoletos. Tal vez hoy una función de búsqueda devuelve “null” al buscar un elemento que no existe, pero si mañana alguien la cambia para lanzar una excepción y no actualiza el Javadoc, el compilador no protestará y el Javadoc quedará obsoleto. Sin embargo, si hay un test que comprueba que, al pasarle un elemento inexistente, la función devuelve “null”, cuando esta persona haga el cambio el test fallará, así que tendrá que actualizar el test (o dejar la función como estaba). Por tanto, los tests de unidad son una documentación que nunca queda obsoleta.

Por supuesto, los tests de unidad no sirven de nada si nadie les presta atención. En muchas organizaciones tienen políticas que obligan a ejecutar los tests de unidad antes de hacer “commit” (por supuesto, los tests tienen que pasar con éxito). En algunos sitios tienen “compiladores continuos” (“continuous build”), que son máquinas que toman la última versión del software, lo compilan, ejecutan los tests de unidad y avisan si alguno falla. En otros sitios integran los tests de unidad en el sistema de control de versiones, de forma que no se puede hacer “commit” si algún test falla.

En posteriores historias explicaré cómo escribir tests de unidad, cómo usar inversión de dependencias, mocks y objetos falsos para aislar la unidad que queremos probar, y cómo hacer desarrollo dirigido por los tests.

(Siguiente artículo).