Por qué conviene usar programación orientada a test TDD

El desarrollo de software programando con TDD se ha convertido en una de las compentencias más deseadas en las empresas. Ofrece garantías de funcionamiento, protege de fallos ante evoluciones y valida la correcta aplicación de los componentes. Averigua por qué conviene usar TDD

Qué es el TDD

TDD son las siglas de Test Driven Development o desarrollo orientado a test y corresponde con una manera de abordar los desarrollos de aplicaciones móviles o la programación en general de sistemas de información. Se conoce como una metodología de desarrollo y se basa en definir casos de usuario usando el mismo lenguaje de programación, y que por tanto pueden ser ejecutados de forma automática, para comprobar que la aplicación (aun no programada) da soporte a todos y cada uno de los requisitos de usuario registrados. La correcta ejecución de todos los casos de usuario, determina la adecuación de la solución implementada.

Por tanto, puede decirse que aplicar TDD se separa en dos fases bien diferenciadas. La primera de ellas es recopilar todas las funcionalidades que se espera del sistema de información en forma de varios (muchos) test unitarios. Mientras que la segunda, trata de implementar (programar) el cuerpo de la aplicación para que todos los test se ejecuten correctamente. Para llevar a cabo la segunda fase se deberán programar todos los elementos que finalmente compondrán la aplicación en sí misma.

Beneficios de usar TDD en un proyecto de software

La programación orientada a test cubre dos aspectos importantes en el desarrollo de software. Permite delimitar el alcance de un poryecto, ya que todos los requisitos de usuario deberían estar reflejados en casos de test. En muchas ocasiones, la enumeración de los test se considera parte del contrato que el proveedor de los servicios de programación deberá llevar a cabo. Y al mismo tiempo, será el justificante de entrega de un proyecto funcionando correctamente por parte del proveedor.

La ejecución correcta de los casos valida la implementación y por tanto el trabajo realizado, así como también ofrece garantías del correcto funcionamiento ante evoluciones y la subsanación de errores.

Cómo implementar la metodología TDD

Prácticamente todos los lenguajes de programación modernos, juntamente con los frameworks de programación que les acompañan porporcionan funcionalidades, ya sea nativamente o mediante librerías de terceros, para poder ejecutar test unitarios automáticamente durante la fase de construcción del programa. Si alguno de los test falla, el binario final no se construye, mostrando al usuario qué test no superaron la prueba, ya sea porque ocurrió un error o porque retornaron un resultado incorrecto.

Se debe tener presente que para aplicar TDD se requiere un estilo de programación muy concreto, en donde los programadores realicen unidades funcionales fáciles de invocar por parte de un test. Esto requiere que el programa tenga un buen diseño de componentes y haga una buena gestión de depencencias entre éstos. Ya que un test debe tener un acceso directo y simple a la invocación de una unidad funcional cualquiera, sin tener que instanciar cientos de componentes para poder consumir un método determinado. Así que es básico realizar una buena descomposición de componentes y separación de responsabilidades.

Sin embargo, los programadores juniors suelen carecer de la visión general y la experiencia necesaria para realizar un buen diseño de componentes que sean fáciles de instanciar y utilizar y que sólo respondan a una responsabilidad concreta y reutilizable. Es con el tiempo o con ayuda de otros programadores seniors, que alcanzarán la madurez necesaria para aplicar TDD correctamente. Aun así conviene introducir TDD en las fases iniciales de todo proyecto de software y guiarles en el proceso.

En el caso de realizar la aplicación con Java, los test se escriben como clases y métodos. La librería más habitual para ejecutar estas clases en forma de Test se denomina jUnit, y la herramienta de construcción Maven se encarga de lanzar a ejecución todas las clases marcadas como test vía jUnit de forma automática.

Los test se consideran código fuente del proyecto, y por tanto debe formar parte del código bajo el control de versiones. Así que, si los ficheros fuente Java están habitualmente en src/main/java, las clases de testing estarán en src/test/java.

Cómo se escribe un test en TDD

La respuesta dependerá del lenguaje de programación. Pero prácticamente todos ellos siguen un mismo patrón o pseudo código en donde hay unas fases bien diferenciadas por cada caso unitario. Su implementación puede variar en función del tipo de proyecto donde se intenta implantar TDD. En proyectos nuevos será mucho más sencillo de introducir, que en aquellos ya existentes y programados sin tener presente el TDD, por el hecho de que en proyectos nuevos suele ser más fácil realizar una buena separación de componentes. Aun así, cualquier test debe tener los siguientes pasos.

Fases de un test unitario:

  1. Instanciación del componente. Los test están destinados a comprobar un caso sobre un componente. Conviene mantener a los test especializados en una determinada situación. Es un error escribir casos que prueben muchas funcionalidades. Así que en esta fase, el programador debe recuperar o crear sólo una instancia del componente o unidad funcional que quiere probar
  2. Ejecución del componente. Dado el componente recuperado del paso anterior, lo siguiente es invocar la ejecución del mismo, ya sea una función o un método de una clase. Esta invocación se debe realizar con unos parámetros conocidos, de tal manera que se sepa de antemano cuál es el resultado que deberá ofrecer si el funcionamiento es correcto.
  3. Comprobación del resultado. Al realizar la llamada de la unidad funcional conociendo los argumentos y suponiéndose que es una funcionalidad determinista (es decir, que ante los mismos parámetros, siempre ofrecerá el mismo resultado) se comprueba el resultado obtenido por la ejecución de dicha lógica. Si el resultado proporcionado es igual al esperado el test pasa correctamente. Si no, se lanza una excepción que avisa del error. Éste, suele provocar la finalización del proceso de construcción de la aplicación.

Es importante recalcar que los test deben comprobar únicamente una funcionalidad. Deben ser pequeños y concisos, así que en cualquier proyecto se pueden acumular cientos de test con normalidad. Los test pequeños y concretos son más fáciles de mantener, de probar y sobreviven con más frecuencia a los cambios de las aplicaciones.

Cómo programar con TDD con Spring Boot

Si ya tenemos un proyecto realizado con Spring como el resultante de seguir los pasos que se indican en cómo realizar una aplicación con Spring Boot, incorporar TDD es muy sencillo ya que Spring aporta las dependencias y guías de cómo realizar el testing para programas de este tipo.

Si el proyecto está hecho con Maven, con Gradle sería de manera similar, basta incluir la dependencia base que se encarga de agrupar todas las librerías para realizar y ejecutar los test. La dependencia a incluir en el pom.xml es la siguiente:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
    

Incluir la versión en la dependencia no es necesario si el proyecto tienen como parent el proyecto de Spring como se indica en el tutorial. En cualquier caso, la inclusión de esta aporta al proyecto la librería jUnit entre otras, necesario para hacer el primer test.

Acto seguido, el programador puede crear un test simple en la ubicación indicada como se muestra a continuación:

// src/test/java/org/eadp/test/MyTest.java

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
public class MyTest {

    @Test
    public void myTest() {
        // Fase de inicialización
        MyComponent mc = new MyComponent();

        // Fase de ejecución
        int res = mc.doSomeLogic(1L);

        // Fase de comprobación
        // se espera que el resultado (res) sea igual a 1 (Long)
        Assert.assertEquals(1L,res);
    }
}

Spring proporciona un runner que aporta la instanciación de todo el contexto de Spring y por tanto la gestión de dependencias dentro de los test. Así el programador puede inicializar los componentes requeridos mediante new, o usar la inyección de dependencias mediante @Autowired como un atributo de la clase MyTest. Si quieres aprender más del uso de Spring y cómo funcionan los contextos puedes visitar el artículo introducción a los contextos de Spring y a la inyección de dependencias

El programador deberá crear un gran número de clases y métodos similares a estos para tratar de cubrir la mayor parte de funcionalidades implementadas. Incluso se debe recurrir a test que prueben diferentes caminos de la misma lógica, por ejemplo cuando hay un if-else, se añaden dos casos de test, uno por cada bifurcación del condicional para asegurar que todas las líneas de código son ejecutadas al menos una vez.

La ejecución del o los test puede hacerse habitualmente directamente desde el editor de programación que se use. La mayoría de las veces basta con posicionar el cursor encima del test y pulsar el botón derecho del ratón. En el menú contextual suele aparecer "ejecutar test" o "depurar test". Y si no, siempre se puede forzar la ejecución mediante la sentencia Maven correspondiente:

mvn test

La salida de Maven tras ejecutar los tests indica el número de test ejecutados y cuántos tuvieron una respuesta satisfactória

[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
    

A partir de aquí, es tarea del equipo de programadores crear todos y cada uno de los casos de test que se recojan del catálogo de requisitos. Los test deben ser programados en un lenguaje Java de alto nivel, creando las clases de componentes o servicios necesarios, juntamente con aquellas destinadas a transportar la información requerida en cada paso. Siempre haciendo una buena descomposición de componentes según su responsabilidad.

La buena separación en componentes o clases pequeñas y especializadas facilitará su verificación en test y la reutilización de la lógica escrita entre los diferentes módulos del aplicativo.

Conclusiones

Aplicar TDD desde un inicio de cualquier proyecto es muy beneficioso tanto en garantías del trabajo realizado como en la delimitación del alcance del proyecto. Para definirlos se requiere que el usuario o cliente haga un traspaso del conocimiento que tiene y lo que espera cubrir con el sistema de información. Es una tarea importante ya que se verá obligado a deshacer las posibles ambigüedades que aparezcan y sobre todo antes de comenzar a implementar ninguna función.

Si eres usuario de TDD, estaremos encantados de que compartas con nosotros tus experiencias ya sean positivas o negativas y así ampliar mas la visión sobre la aplicación de una programación orientada a test.

Déjanos tu opinión!