Mi "Definition of Done"

El otro día en el podcast de @HablandoConTechLeaders entrevistaron a @Javier de Arcos, si no lo habéis escuchado estáis tardando, y entre otras muchas cosas hablaron del concepto Definition of Done a nivel de empresa y como éste puede cambiar de unas a otras. A nivel profesional es algo que me toca de cerca, ya que en los últimos años este término ha cambiado completamente y me gustaría explicar por qué.

Como concibo el desarrollo de software a día de hoy

Hace unos días comentaba que se cumplían dos años desde que empecé a trabajar en mi actual empresa. Este cambio de empresa ha supuesto un cambio radical en mi forma de entender el desarrollo de software y, por tanto, en lo que yo entiendo por Definition of Done.

A día de hoy, hay una serie de fundamentos no negociables en mi forma de entender el desarrollo que han cambiado completamente respecto a mi yo de hace dos años. A grandes rasgos algunos de ellos son:

  • La persona que se desarrolla es owner más allá del código.
    • Los pipelines son una parte más del desarrollo.
    • El despliegue es una parte más del desarrollo.
    • La monitorización es parte del desarrollo.
  • Creo en los equipos de QA pero como extra, no como red de seguridad.
    • La calidad es responsabilidad de la persona que desarrolla.
    • QA no está para encontrar bugs.
    • El código debe estar testeado por la misma persona que lo ha creado.
  • Los tests deben ser ciudadanos de primera clase.
    • El código no está terminado hasta que no está testeado.
    • Los tests deben ser fiables.
  • Sin feedback por parte de los usuarios todo lo anterior no sirve de nada.
    • El feedback se debe recibir lo antes posible.
    • La comunicación con el usuario tiene que ser constante.

¿Cuál es mi “Definition of Done”?

En mi caso particular me gusta desarrollar aplicando arquitecturas limpias, como puede ser Ports and Adapters, y esto tiene una serie de implicaciones técnicas a la hora de definir el Done en una tarea.

Por tanto, mi Definition of Done es algo así:

  • Debe existir un test de aceptación que valide el sistema de extremo a extremo.
    • Posteriormente, veremos como en función del tipo de caso de uso este test será real o unitario.
  • Deben existir tests unitarios para validar la orquestación de los casos de uso.
    • Tanto el happy como los principales unhappy paths.
  • Deben existir tests de integración para validar los servicios externos.
    • Si hay ciertas acciones que no se pueden producir de forma deliberada desde fuera del servicio externo, simularemos este escenario con tests unitarios.
  • El código cumple con el estándar del equipo definido en el pre-commit.
    • Tiene el formato correcto.
    • Los tipos están bien definidos.
    • Las variables están bien nombradas.
    • No hay errores en el linter.
  • La documentación está actualizada.
  • La tarea esta probada en producción, tanto en happy como en unhappy paths.
  • Los usuarios son notificados sobre estas nuevas capacidades del sistema y tenemos feedback de ellos.

Por si hay alguna duda, aquí dejo lo que para mí es un test unitario vs integración.

Comandos

Entenderemos como comando, a un caso de uso que cambia el estado del sistema (ya que tiene side effects).

Casos prácticos de un comando pueden ser:

  • Crear/Borrar/Editar un recurso en AWS.
  • Enviar un email.
  • Enviar un mensaje a un chat.
  • Crear un ticket en Jira.
  • Hacer un pago.
  • Hacer rollback de un despliegue.

Debido a que estas acciones cambian el estado del sistema, es importante ajustar nuestra suite de tests. Si es posible crear/borrar/enviar los recursos en un entorno controlado mejor. Pero si no es el caso, debemos reproducir esta situación con tests unitarios.

Caso práctico de un comando

Tenemos que implementar una nueva funcionalidad en nuestra aplicación para que nuestros usuarios puedan crear ECRs en AWS.

Lo primero que me gusta hacer, y revisar posteriormente durante el desarrollo, es refinar de una formar intensa la tarea asociada de tal forma que sea la fuente de verdad del estado actual de la funcionalidad.

Una vez realizado este análisis, mi lista de puntos a completar para definir mi Done sería la siguiente:

Entre paso y paso de esta lista se hace commit, push y despliegue a producción siempre.

  • Creamos un test de aceptación unitario que nos permita validar de extremo a extremo la creación de ECRs (este test no pasará hasta que se acabe la funcionalidad).
    • Este test se marca como skip para que no se ejecute en los pipelines.
  • Como es necesario interactuar con AWS, crearemos (aplicando TDD) un repositorio cuya única responsabilidad sea interactuar con ECR.
  • Para validar el correcto funcionamiento de este nuevo repositorio, crearemos varios tests de integración probando tanto la correcta creación como los principales posibles fallos.
  • Una vez que estamos integrados con AWS para crear ECRs, crearemos el caso de uso encargado de recibir la petición por parte del usuario y usar el repositorio que crea ECRs.
  • Para validar el correcto funcionamiento del caso de uso y haciendo TDD, debemos ir creando los diferentes tests unitarios que nos permitan validar que todo está bien orquestado.
  • Una vez que el caso de uso este implementado, el test de aceptación debería estar en verde.
    • Se quita el skip de este test para que se ejecute en los pipelines.
  • En este punto nuestro sistema ya permite crear ECRs, por lo que solo faltaría hacerlo visible en producción, crear algunos ECRs y cuando tengamos la confianza suficiente, comunicarlo a los usuarios.
  • Una vez recibido el feedback por parte de los usuarios pueden pasar dos cosas:
    • El comportamiento no es el esperado, por lo que usamos el feedback para ajustar y volvemos a completar nuestro Done.
    • El comportamiento es el esperado, damos la tarea por cerrada y a otra cosa.

Queries

Entenderemos como query, a un caso de uso que no cambia el estado del sistema (no tiene side effects).

Casos prácticos de una query pueden ser:

  • Listar los Buckets de tu empresa.
  • Buscar un usuario por su email.
  • Listar los usuarios de tu sistema.
  • Buscar los componentes de un equipo en tu organización.

Debido a que estas acciones no cambian el estado del sistema, para mí siempre debe existir un test de aceptación real probando el sistema de extremo a extremo.

Caso práctico de una query

Tenemos que implementar una nueva funcionalidad en nuestra aplicación para que nuestros usuarios puedan buscar los buckets en S3.

Una vez refinada la tarea como en el punto anterior, mi lista de puntos a completar para definir mi Done sería la siguiente:

Entre paso y paso de esta lista se hace commit, push y despliegue a producción siempre.

  • Creamos un test de aceptación real que nos permita validar de extremo a extremo la búsqueda de Buckets en AWS (este test no pasará hasta que se acabe la funcionalidad).
    • Este test se marca como skip para que no se ejecute en los pipelines.
  • Como es necesario interactuar con AWS crearemos, aplicando TDD, un repositorio que su única responsabilidad sea interactuar con S3.
  • Para validar el correcto funcionamiento de este nuevo repositorio crearemos varios tests de integración probando tanto la búsqueda con resultados, como los principales posibles fallos.
  • Una vez que estamos integrados con AWS para buscar Buckets, crearemos el caso de uso encargado de recibir la petición por parte del usuario y usar el repositorio que busca Buckets.
  • Para validar el correcto funcionamiento del caso de uso y haciendo TDD, debemos ir creando los diferentes tests unitarios que nos permitan validar que todo está bien orquestado.
  • Una vez que el caso de uso este implementado, el test de aceptación debería estar en verde.
    • Se quita el skip de este test para que se ejecute en los pipelines.
  • En este punto nuestro sistema ya permite buscar Buckets por lo que solo faltaría habilitarlo en producción, buscar algunos Buckets, buscar Buckets que no existen y cuando tengamos la confianza suficiente comunicarlo a los usuarios.
  • Una vez recibido el feedback por parte de los usuarios pueden pasar dos cosas:
    • El comportamiento no es el esperado por lo que usamos el feedback para ajustar y volvemos a completar nuestro Done.
    • El comportamiento es el esperado, damos la tarea por cerrada y a otra cosa.