5º Nivel: Tests
Principio ágil
Software que funcione es la principal medida del progreso.
Y si no está testeado, no se puede afirmar que funcione.
Descripción
Antes de desplegar a la infraestructura virtual, el código tiene que ser testeado, porque código que no está probado, está roto. El crear los tests unitarios y, eventualmente, una forma automática de ejecutarlos es un paso previo a llevar a cabo tareas de integración y despliegue continuo (que se verán a continuación).
Prerrequisitos
Haber pasado los niveles anteriores, automatización y configuración del sistema de CI. Las decisiones tomadas en los mismos tendrán que estar registradas en los documentos correspondientes.
Conceptos fundamentales
Si el nivel 2 se centraba en la puesta en práctica de conocimientos sobre estructuras de datos, en este caso hay que aplicar los conocimientos sobre programación adquiridos.
- Una vez más, es imprescindible conocer cuales son las buenas prácticas a la hora de escribir código. Es conveniente, por ejemplo, conocer los principios SOLID, de los cuales el más importante, en este nivel, es el principio de responsabilidad única. Otros principios, tales como DRY (Don’t repeat yourself), conviene que se tengan en cuenta para escribir código eficiente y que se pueda evolucionar fácilmente. Conocer filosofías alternativas como CUPID es deseable, pero en todo caso el concepto principal es que escribir código consiste en la aplicación de una serie de metodologías, no sólo crear algo que sea sintácticamente correcto.
- La lógica de negocio, para añadir algún tipo de valor, necesita resolver problema habitualmente usando un algoritmo. Hay que tener conocimiento de ciertos algoritmos básicos como ordenación, el problema de la mochila, algoritmos voraces o simplemente entender bien el concepto de algoritmo como método sistemático, con resultado conocido, que resuelve algún problema.
- El concepto de excepciones es importante conocerlo y manejarlo a este nivel, porque es la forma de señalizar de una parte a otra de una aplicación si hay algo incorrecto y si hay alguna forma de recuperarse de ese estado incorrecto.
Explicación
En sistemas de desarrollo ágil quien desarrolle tiene que asegurar que el código cumple todos los deseos del cliente antes de ser desplegado o simplemente incorporado a la rama principal; los tests son la implementación de esos requisitos.
Y los requisitos salen del problema original, pasando por las historias de usuario y los issues correspondientes.
Para ello se escriben una serie de tests que (eventualmente) se ejecutarán automáticamente al añadir o modificar código o cuando se solicite un pull request. Estos tests sobre el código tienen el fin obvio de asegurar la calidad del mismo, pero también en un entorno de desarrollo colaborativo permiten integrar código fácilmente asegurándose de que no se rompa nada. Si no está testeado, está roto, es el lema del desarrollador. Al ser la plasmación de las especificaciones, en cada incorporación de código la ejecución automática de los tests asegura que el código sigue cumpliendo las especificaciones.
Los tests son de funciones (o métodos) y unitarios, y como tales lo pasan las funciones, y lo hacen de forma independiente. De hecho, en TDD (desarrollo basado en test, Test Driven Development) se escriben los tests antes que las funciones; continuando con la gestión del proyecto iniciada en el objetivo anterior, las especificaciones y las funcionalidades tendrán que estar en un issue y este en un hito. No se cerrará ningún issue y ninguna historia de usuario sin hacer el test correspondiente que se asegure que funciona.
Evidentemente, en este nivel es cuando hay que introducir la lógica de negocio, porque lo principal que se va a testear es esa lógica que añade valor al cliente. No hace falta que se introduzca toda la funcionalidad de la misma, pero antes de hacer cualquier despliegue hay que hacerlo, así que es mejor empezar ya; por ejemplo se puede introducir alguna lógica de validación, que va a ser previa a la lógica de negocio (e imprescindible si se usan lenguajes que no tengan un sistema de tipos adecuado). Si en el objetivo anterior sólo se han declarado objetos valor, en este tendrá que haber entidades, que son los módulos que contienen la lógica de negocio.
Elección de herramientas
Las herramientas para tests se dividen en varios niveles de abstracción, y la denominación de las mismas varía bastante con el lenguaje. Algunos lenguajes incluyen los llamados marcos o frameworks de test, que las incluyen todas; pero de forma bastante confusa, se puede llamar framework a algunas herramientas que incluyan sólo dos de estos niveles. Pero veamos cuales son, de los más bajos a los más altos.
- Aserciones: Las aserciones, a veces también llamados “comparadores” o matchers. Se trata de funciones que comparan la salida obtenida con la deseada, añadiendo un mensaje personalizado si la comparación es positiva. En algunos casos, estas aserciones producen directamente una salida que sigue TAP, test all o test anything protocol, un simple protocolo de texto que emite
OK
oNOK
más un número, y que permite saber si un test específico ha pasado o no y qué número es. Hace alguna cosa más, pero esa es la idea básica. Las aserciones son simplemente funciones, o siguen el estilo BDD, que se asemeja más al lenguaje natural, usando “frases” más completas (en vez de simples funciones). - Test runners o frameworks: en algunos casos las aserciones no producen TAP; en otros simplemente es necesario agrupar las aserciones en subtests, o simples grupos con un título un poco más descriptivo. Adicionalmente, pueden incluir fixtures o funciones que crean objetos que se van a usar en todos los tests, u otras funciones que se ejecutan antes o después de cada test o de todos los tests. También suelen contener funciones para agrupar tests en subtests, planificar los mismos (indicar de antemano, por ejemplo, cuantos tests se van a ejecutar).
- Herramientas CLI para ejecutar los tests, que para aumentar la confusión, a veces se llaman también test runners, porque efectivamente ejecutan los tests: buscan los diferentes scripts que ejecutan los tests, analizan la salida TAP y producen un informe. Adicionalmente, pueden hacer también tests de cobertura (que analizan qué sentencias, funciones y ramas se han usado efectivamente en los tests). Por otro lado, hay lenguajes o marcos de programación, como Go o
deno
, que ya incluyen una subordentest
que realiza esta labor; en esto casos no habrá que establecer, como es natural, un criterio para elegirlo, porque la mejor práctica es usar la que ya hay, pero si habrá que dejar claro en la documentación del objetivo que este es el caso y que se va a hacer de esa forma.
En general, se habla de testing framework cuando incluye dos o tres de estas características. Conviene en todo caso consultar el panorama (y las mejores prácticas) en el lenguaje de nuestra elección para aclarar qué herramientas existen, qué funcionalidad precisa tienen, y finalmente elegir lo más conveniente.
Tal como se ha hecho en el objetivo anterior, hay que llevar a cabo una elección documentada de las herramientas que se van a usar. Aparte de los criterios que se quieran establecer, habrá que tener en cuenta que esta herramienta se va a usar en el objetivo siguiente, donde uno de los requisitos principales es que no se puede escribir en el directorio en el que están los fuentes, por lo que si la herramienta efectivamente escribe algún tipo de lockfile o similar, tendrá que ser configurable para que pueda escribir en un lugar donde sí haya permiso.
En general, todas las herramientas se van a seguir usando el resto del proyecto, por lo que mirar siempre hacia adelante a ver si cubren los requisitos correspondientes es conveniente.
Recursos adicionales
El material del curso cero incluye varios temas relacionados con el desarrollo basado en test. Se recomienda encarecidamente que se entiendan los conceptos.
-
Como crear objetos de test.
-
Como ejecutar los tests.
Lista de comprobación
En este objetivo se sigue la metodología de desarrollo de todos los objetivos, como es natural. Especialmente, hay que seguir usando issues para avanzar en el código. Por eso conviene repasar la lista de comprobación de los mismos, incluida en otro objetivo.
Copia y pega en tu PR, lleva a cabo esta lista de comprobación sobre tu código, y marca todo lo que hayas hecho.
* [ ] ¿Hay un camino claro que permita ir desde el código hasta la historia de
usuario que desarrolla, pasando por el mensaje de commit y el issue
correspondiente?
* [ ] ¿Se ha intentado cubrir con tests una parte de la lógica de negocio que se
esté desarrollando?
* [ ] ¿Hay un producto mínimamente viable que describa lo que se va a entregar en
este objetivo?
* [ ] ¿En este producto mínimamente viable se han priorizado unas clases,
módulos o paquetes más fundamentales frente a otros, movidos a otro
PMV/Hito?
* [ ] ¿Estoy leyendo todo lo que marco o simplemente marco lo que se me pone por
delante?
* [ ] ¿Se han documentado las elecciones de biblioteca de aserciones y del
de *test runner*?
* [ ] ¿Te has asegurado que lo que mencionas como biblioteca de aserciones y
test runners lo son realmente y tienen la misma funcionalidad? ¿O estás poniendo
`unittest` como *test runner* y `pytest` como biblioteca de aserciones?
* [ ] ¿Esa documentación incluye criterios de aceptación y también criterios de
búsqueda para las diferentes opciones?
* [ ] ¿Se han seguido los principios F.I.R.S.T. en la creación del test y
se ha documentado cómo se ha hecho?
* [ ] ¿Se describe claramente en el PMV los tests que hay que pasar para que se
considere viable?
* [ ] ¿Se han testeado también las excepciones?
Entrega del PR
Para empezar, una de las medidas que vamos a llevar a cabo en este nivel es cambiar de equipo al menos una persona, que irá a otro equipo seleccionado por el profesor. En la empresa es habitual hacer este tipo de cosas, por falta de recursos o simplemente porque se termine un proyecto determinado y haya que trabajar en otro. La velocidad con que esta persona se incorpore mostrará la calidad de las herramientas y documentos de onboarding que se hayan llevado a cabo.
Como casi todos los niveles, este tiene dos fases: primero hay que seleccionar las herramientas, y a continuación comenzar a trabajar con ellas. Se aconseja, como se debe haber hecho en otros niveles, que se cree una rama (y un PR) con la documentación sobre esta decisión (que luego se convertirá en ADD, es para que el profesor lo examine) y una vez superada esa parte se proceda a repartir el trabajo en una reunión sobre escritura de los diferentes aspectos de la lógica de negocio y sus tests correspondientes.
Se tendrá que haber actualizado el repositorio que se usara en los objetivos anteriores y añadir al fichero de este hito la URL del PR en el repositorio que haya correspondido, y la versión.
La descripción del proyecto tiene que seguir progresando, así que el README.md
tiene que incluir una descripción real de la clase que se vaya a testear, como instalarla y testearla y qué uso va a tener dentro del servicio (o servicios) web que se van a desplegar más adelante. En un proyecto el README.md debe estar escrito para la persona que llegue, y no para que se corrija. Si hay documentación adicional, tendrá que enlazarse desde este README.md
.
Habrá que añadir al fichero is.yaml
las siguientes claves:
test
apuntará a uno de los ficheros que se use para testear.
Objetivos a alcanzar
- Aprender a elegir herramientas para llevar a cabo tests.
- Aprender a plasmar la lógica de negocio en código, y a testear su correcto funcionamiento mediante los tests. Este es evidentemente el subobjetivo más importante de este objetivo.
- Aprender a integrar las tareas como esta en la herramienta que haya de gestión de tareas.
Superación del nivel
Cuando se haya testeado de forma adecuada la lógica de negocio y pasen todos los tests; el código tendrá que seguir las buenas prácticas habituales y estar aprobado por el profesor, aparte de otras dos personas del equipo.
A dónde ir desde aquí
El buen código debe documentarse. Convendría pararse antes del siguiente nivel para hacerlo.