Ya son varias las ocasiones en las que sale a relucir el término test unitario en alguna conversación y me llama la atención que no todos llamamos unitario a lo mismo. Por ello me gustaría hablar de lo que son para mí los tests unitarios a día de hoy y porque creo que es la definición para describirlos.
Definición clásica de tests unitarios e integración
La definición clásica de tests unitarios es que son tests que testean una unidad, entendiendo como unidad normalmente un método o una clase. Por otra parte se suele definir como test de integración aquel test que integra diferentes partes de nuestra aplicación.
Para mí esta era la definición que conocía y usaba hasta que cambie de trabajo hace casi un año y me encontré que el equipo había decidido definir de otra forma que eran estos conceptos. Tengo que admitir que al principio me resulto extraño, pero con el paso del tiempo tengo que decir que para mí esta forma de definir que es un test unitario encaja mucho mejor con mí forma de programar.
Esta nueva definición de que se considera unitario y que integra se basa
en los principios F.I.R.S.T
que vamos a conocer a continuación.
Principios F.I.R.S.T
El término F.I.R.S.T
fue acuñado por Robert C. Martin en su
libro Clean Code: A Handbook of Agile Software Craftsmanship con la
intención de ayudar a los desarrolladores a escribir mejores tests siguiendo una
serie de 5 sencillas reglas.
Estas reglas están especialmente pensadas para los tests unitarios, pero se pueden aplicar perfectamente a cualquier tipo de tests.
Rápidos (Fast)
Para mí sin duda el principio más importante. Los tests deben ser los más rápidos posibles (hablamos del rango de tiempo de pocos segundos) permitiéndonos tener el mayor número posible de tests de este tipo.
Otras ventajas asociadas con este principio serán:
- Los tests se podrían ejecutar en cada salvado de un fichero.
- Los tests se ejecutarán siempre antes de cada commít.
- Los tests se ejecutarán siempre antes de cada push.
- Los tests se ejecutarán siempre en cada pipeline.
Aislados (Isolated)
Estos tests tiene que ser completamente independiente, es decir no tienen que tener dependencia entre ellos y por tanto se podrán lanzar en paralelo, lo que incrementará aún más si cabe la velocidad de los mismos.
Repetibles (Repeteable)
Donde se ejecuten estos tests no debe ser relevante para lo mismos, nuestros tests unitarios deben pasar exitosamente con independencia de en que entorno los ejecutemos, esto aplica a que tanto en nuestra CI como en cualquier máquina de los miembros del equipo los tests deberán comportarse exactamente igual.
Auto Validables (Self validating)
Los propios tests deben ser suficientemente autónomos para saber por qué fallan y no que su éxito o fallo dependa de factores externos.
Oportunos/Exhaustivos (Timely/Thorough)
Estos 5 principios encajan a la perfección con técnicas como TDD donde debemos crear los tests justos y oportunos para cubrir el happy path y los posibles errores más evidentes de la lógica concreta que estemos desarrollando.
¿Que son los tests unitarios para mí?
Por tanto después de hablaros de estos principios F.I.R.S.T
me gustaría
contaros lo que yo entiendo por test unitario.
Para mí son todos aquellos tests que siguen los principios F.I.R.S.T comentados anteriormente, por tanto deben ser lo más rápidos posibles y deberán hacer uso de dobles muchas veces para poder lograrlo, esto puede ser forma parcial si conseguimos mantener el tiempo de ejecución bajo de los mismos.
Unitario por tanto no va a estar asociado a una clase como sinónimo de unidad (definición clásica), sino que hablaremos de unidad más a nivel de comportamiento.
Ejemplos de este tipo de tests puede ser:
- Caso de uso con varios colaboradores, que serán dobles, donde validaremos el caso de uso como unidad.
- Controlador de una API REST usando dobles para todos los componentes involucrados, en este caso testearíamos que los endpoints de la API son válidos.
- Clases de dominio con lógica interna.
¿Qué son los tests de integración para mí?
Como ya podéis estar pensando, la definición previa de test unitario cubre una parte inmensa de los posibles tests que podemos escribir y por tanto lo que para mí son tests de integración es un grupo muy reducido de tests.
Serán aquellos tests que se comunicán con el exterior, ya sea de forma real contra servicios externos o de forma simulada contra soluciones como puede ser Testcontainers, debido al tipo de comunicación que tienen estos tests es de esperar que serán más lentos que los unitarios y por tanto serán drásticamente menores en número.
Una aproximación que me gusta mucho es tener métricas sobre el tiempo de ejecución de cada test de tal forma que nos permita detectar los más lentos y ejecutarlos únicamente en el CI/CD (donde siempre se deberán ejecutar todos los tests, sin excepción).
Ejemplos de este tipo de tests puede ser:
- Validar la integración con una API externa, como puede ser Slack o Airtable.
- Validar la integración con AWS.
- Validar la integración usando el SDK de Google.
Conclusiones
Espero que os haya resultado interesante esta nueva definición, solo recalcar que no es mejor ni peor simplemente es otra forma de definir nuestros tests.
Lo más importante es que el equipo llegue a un acuerdo sobre esta definición y que luego se aplique por todos.
!Un saludo!