Organizando los tests

Los tests son importantes, eso ya lo sabemos y los gurús de Twitter no dejan de repetirlo: «El testing es una práctica fundamental para garantizar la calidad»… pero ¿cómo podemos sacarle más partido a los tests? Con los tests podeos detectar defectos y lo más importante es que hacemos el software fácil de modificar en el futuro, básicamente los tests nos dan confianza en el sistema. Para llegar a esa confianza tenemos distintas estrategias, como la pirámide de los test: los tests deben organizarse en una pirámide con tres capas: tests unitarios, tests de integración y tests de aceptación… pero ¿hay otras alternativas? y de manera física, como donde ponemos esos tests ¿en que carpeta iría este,.. ¿cómo organizar los tests? Además también tenemos los tests de regresión, los test de carga,.. Como podemos ver el tema del testing es muy extenso y podemos hablar de muchas cosas. Incluso en el blog ya ha hemos escrito sobre estrategias para escribir mejores tests, sobre «la jerga del testing», no solo una sino dos veces) y hace ya bastante tiempo hablamos sobre los mitos del testing(como que el software con test esta libre de bugs). Así que hoy vamos a intentar organizar un poco todo esto.

Introducción

Vamos a dejar claro que entendemos por tests. Los tests van a ser ficheros que contienen código que que sirve para evaluar o verificar el comportamiento de una parte del sistema. Normalmente utilizamos frameworks como phpunit para que nos ayuden con este propósito. Por otro lado también tenemos ficheros de configuración para indicar como ejecutar los tests. Por último también podríamos tener ficheros de soporte, como por ejemplo abstract tests clases, helpers, fixtures, que podemos organizar en diferentes archivos.

Por todo esto, es importante saber donde vamos a ubicar los test dentro del proyecto. Como siempre esta organización puede variar dependiendo del contexto: el tipo de proyecto, el equipo, los tipos de tests que vamos ha hacer,… en este post mayormente hablaremos de PHP porque es donde más experiencia tengo, quizás esto no sea aplicable al 100% a otros lenguajes.

¿Cómo podemos organizar los test dentro de un proyecto?

Antes de eso vamos a comentar cual será nuestro propósito a la hora de escribir los tests. Al principio, cuando no tenemos ningún test pues el objetivo es tener alguno, por lo que la ubicación del mismo no nos importa mucho: podríamos tener un único archivo que contenga todos los tests (p.e. una aproximación con golden master), o incluso Approval Testing. El problema viene cuando a medida que añadimos más tests, empezamos a tener dificultades para encontrar los tests o para añadir un nuevo caso. Por lo que tal vez, lleguemos a la conclusión de que es mejor tener un fichero de test por cada clase utilizando un sistema de sufijos (añadir *Test al nombre de la clase nos dará el nombre del test), así nos será más fácil encontrar los tests.

Otro factor importante y a la vez polémico a la hora de organizar los tests es organizarlos según su tipología: unitarios, funcionales, end-to-end. Dependiendo del tipo (tendremos que llegar a un acuerdo con el equipo para tener un definición de lo que significa unitario, ánimo con eso…) podría ser interesante tener los ficheros separados y organizados de manera diferente.

Y un aspecto que también puede ser limitante a la hora de organizar los tests es el propio framework de test. Dependiendo de la rigidez del framework y del tiempo que queramos invertir en la configuración del mismo, esto podría hacer que tengamos una estructura de carpetas u otra.

No hay test

De lejos es la mejor manera de organizar los test, sino tenemos tests no tenemos que preocuparnos de como organizamos los test. Tampoco es necesario pensar en configuraciones de frameworks ni nada por el estilo.

Si esto funciona en tu contexto, perfecto.

Test en el mismo directorio

Podríamos tener los test en el mismo directorio que el código

.
├── src
│   ├── Post.php
│   ├── PostTest.php
│   ├── Helper.php
│   └── HelperTest.php
└── phpunit.xml

Para un proyecto pequeño, unos repositorio con scripts o algo así podría tener sentido esta organización. Por ejemplo, en Go se apuesta por esta estrategia, pero nos podrían surgir una serie de cuestiones que vale la pena hacerse antes de elegir esta configuración:

  • ¿Se podría evitar que los desarrolladores usasen código de los test en producción? Configurar composer para excluir del autoload cada fichero de test puede ser un «jaleo».
  • ¿Cómo se podría distinguir entre test unitarios, funcionales o end-to-end?

Quizás haya más preguntas que podemos plantearnos, escribe un comentario y las vemos.

Test en el mismo directorio

Los test como un subdirectorio dentro del código de producción

.
├── src
│   ├── Test
│   │   ├── PostTest.php
│   │   └── PostMother.php
│   ├── Post.php
│   └── NotEmptyException.php
└── phpunit.xml

Proyectos como Symfony siguen esta manera de organizar los test, debido a que son monorepo.

Es una mejora con respecto a tener todos los test en el mismo directorio ya que hace la configuración de composer más sencilla, es posible crear un namespace solo para esto.

Podrían surgirnos dudas sobre como diferenciamos los test unitarios funcionales o end-to-end, además configurar más de un framework de testing como behat podría complicarse. De la misma manera que configurar mutant testing

Test en el mismo directorio organizados por tipo

Es una variante de la anterior, por lo que tendremos las mismas ventajas y además añadimos que sería más sencillo configurar otros framework de tests o de herramientas de performance como phpbench

.
└── src
    ├── Test
    │   ├── Performance
    │   │   ├── PostBench.php
    │   │   └── phpbench.json
    │   ├── Unit
    │   │   ├── PostTest.php
    │   │   ├── phpunit.xml
    │   │   ├── ValueCanNotBeBlankTest.php
    │   │   ├── ValueCanNotBeEmpty.php
    │   │   └── ValueCanNotBeEmptyTest.php
    │   └── Util
    │       └── Helper.php
    ├── Post.php
    ├── ValueCanNotBeBlank.php
    └── ValueCanNotBeEmpty.php

El único problema que nos encontramos aquí es como decir que test son unitarios, funcionales o end-to-end, pero eso nos daría para otro post. En mi opinión, los test unitarios son los que no dependen de recursos externos (base de datos, api externas, acceso a ficheros,…). Los test funcionales son los que prueban una funcionalidad, lo que podría ser un servicio de aplicación, por lo que acceden a base de datos, aunque podrían no acceder a apis externas como facebook, o Twilio. Por ultimo los end-to-end (también conocidos como de aceptación) son test que llaman a nuestra aplicación desde fuera, ya sea a través de un endpoint o de un comando de consola. Para mis estos test prueban que todo está configurado correctamente, es decir que nos hemos «enganchado» al framework correctamente (hemos creado el routing, hemos declarado el servicio para la inyección de dependencias, hemos configurado la BD,…).

Código de tests en un directorio separado

Esta es la manera más común de organizar los test y también es la que PHPUnit recomienda, manteniendo en el directorio de tests la misma estructura de directorios que el código de producción.

.
├── src
│   ├── Post.php
│   ├── ValueCanNotBeBlank.php
│   └── ValueCanNotBeEmpty.php
├── test
│   ├── PostTest.php
│   ├── Helper.php
│   ├── ValueCanNotBeBlankTest.php
│   └── ValueCanNotBeEmptyTest.php
└── phpunit.xml

Con esta configuración obtenemos las siguientes características:

  • Para el equipo, es trivial conocer donde están los tests.
  • Es sencillo configurar composer.json para excluir a los ficheros de test del autoload.
  • También es sencillo configurar phpunit.xml
  • Por contra, distinguir entre tests unitarios, funcionales o end-to-end, sigue siendo complicado.
  • Por contra, igualmente configurar más de un framework de test se hace algo más complejo.

Test en un directorio separado con subdirectorios

En mi opinión esta es la manera más completa. detener organizados los test.

.
├── src
│   ├── Post.php
│   ├── ValueCanNotBeBlank.php
│   └── ValueCanNotBeEmpty.php
├── test
│   ├── Performance
│   │   └── PostBench.php
│   ├── Unit
│   │   ├── PostTest.php
│   │   ├── ValueCanNotBeBlankTest.php
│   │   ├── ValueCanNotBeEmpty.php
│   │   └── ValueCanNotBeEmptyTest.php
│   └── Util
│       └── Helper.php
├── phpbench.json
└── phpunit.xml

Con esta manera es posible distinguir entre código de producción y tests, además también distinguimos entre los distintos tipos de test por lo que es sencillo configurar los diferentes frameworks.

Tenemos el problema planteado anteriormente sobre como distinguir que es un test unitario o funcional, pero creo que es labor del equipo decidir que es cada tipo de test y seguir esa norma.

Conclusiones

Tenemos diferentes maneras de organizar los test, dependeremos del contexto para decidir cuál es la que mejor encaja y por ello debemos tener en cuenta que no escribimos el código en piedra, podemos elegir y movernos a otra en si las circunstancias han cambiado. A mi personalmente me gusta la última opción donde:

  • es posible usar un fichero de configuración para los distintos framework (ej. phpunit.xml)
  • Tenemos los test unitarios separados de los funcionales.
  • El directorio de test unitarios es un espejo de la estructura de carpetas del código de producción, para los funcionales y los de integración no tanto.

Este post es una traducción libre de https://localheinz.com/articles/2023/03/03/organizing-test-code-in-php/

Una respuesta a “Organizando los tests”

  1. Avatar de ¿La piramide del testing? – Jesús L.C. – Apuntes de un aprendiz

    […] tests son importantes, ya hemos dado la turra en el blog bastantes veces: desde cómo organizar los test, estrategias para escribir mejores tests, o incluso hemos hablado «la jerga del […]

    Me gusta

Comenta la entrada

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.

Jesús López

Soy un Ingeniero en Informática y apasionado de la programación. Me gusta disfrutar de mi familia, viajar y perdernos paseando.  Me mola programar, hacer tests y refactorizar código . Practico Test Driven Development (TDD) y me lo paso bien con el legacy codeLeer más

Sígueme en: