Definición de la estrategia de pruebas

Existen muchas clasificaciones de tests, casi tantas como autores. De caja blanca, de caja negra, unitarias, de integración, de aceptación, funcionales, de sistema, de carga, etc, etc.

Hay para todos los gustos y colores. Más aún, algunos tipos de tests tienen un significado distintos para distintas personas. Un caso usual de esto son los test end-2-end que típicamente:

  • para los desarrolladores implican test que de una funcionalidad de formal de atravesar todas las capas de la aplicación, desde la pantalla hasta la base de datos
  • para los testers implican tests que cubren un flujo de negocio que integra varias funcionalidades

Esta situación obliga a que todo equipo deba en primer lugar ponerse de acuerdo en la terminología a utilizar. A partir de esto hay que definir la estrategia de pruebas.

Definir la estrategia de pruebas implica definir:

> Tipos de tests que se realizarán
> Cuantos de esos tests se realizarán
> Quien los realizará
> Cuando los realizará
> Qué herramienta se utilizará
> Y cual será la arquitectura de prueba que dará soporte a todos los tests

La siguiente figura resumen parcialmente la estrategia de pruebas de mi proyecto actual.

Los que indica esta imagen se complementa con las siguientes decisiones:

  • El desarrollo se hace una forma test first, donde los tests guían el desarrollo. Todo empieza “tironeado” por una pruebas de aceptación (story tests) especificada en Gherkin a partir de una charla entre: usuario + tester + developer.
  • Los tests de aceptación no se automatizan al comenzar el desarrollo sino a posterior y al mismo tiempo no todos los escenarios son automatizados.
  • El resto de los tests se escriben a priori del código y de a uno por vez. Una vez implementados se ejecutan constantemente en el build server.
  • Salvo los tests de aceptación, todo el resto de los tests de la figura son escritos por los developers.
  • Para los tests de aceptación usamos Gherkin+Specflow.
  • Para los tests de frontend utilizamos Jasmine+Karma.
  • Para los tests de backend utilizamos NUnit.
  • Para mockear utilizamos Moq y Wiremock.

Adicionalmente a los tests de la figura hacemos pruebas de performance (jmeter) cuando tenemos cambios relevantes en la arquitectura y pruebas exploratorias de ejecución manual antes de cada salida a producción. Tenemos pendiente agregar algunas pruebas de seguridad.

Finalmente respecto de cuántos tests de cada tipo hacer idealmente apuntamos a tener un triángulo con pocos tests de aceptación end-2-end para los flujos centrales en la punta y muchos tests unitarios en la base para lograr cobertura de flujos alternativos. En el medio de la pirámide quedan los tests de componentes/integración.

Materiales de la sesión TDD al banquillo

El viernes pasado estrené esta charla en el meetup online de Software Crafters Chile. La sesión duró poco más de 1 hora y yo quedé muy conforme. Creo que la exposición estuvo bien y la gente se mostró muy participativa. A partir de algunas preguntas/comentarios de los participantes se me ocurrieron algunos updates a la charla para una futura presentación.

Los slides que utilicé están disponibles aquí, allí mismo están las referencias a los papers formales sobre TDD. Como mencioné en la sesión, el paper que me resulta más relevante es el de Fucci, Erdogmus y colegas: “A Dissection of the Test-Driven Development Process: Does It Really Matter to Test-First or to Test-Last?

Para quienes quieran estudiar TDD mis recomendaciones son:

Agradezco a @JosephCastroR y demás organizadores del meetup por la invitación.

Previsibilidad mata velocidad

Completamos la séptima iteración entregando menos de lo que habíamos planificado y eso generó cierto debate en la planning de la octava iteración, llevando incluso a que algunos miembros del equipo quieran replantear la forma de estimación.

Hasta el momento hacíamos lo siguiente:

  • Repasar el backlog candidato determinado por el Product Owner, asegurando que entendíamos cada uno de los ítems
  • Estimar “a ojo” los item que consideramos podríamos llegar a completar en la iteración por comenzar
  • Asignar puntos (1, 2 o 3) a cada uno de los items, principalmente para estar seguros que todos los miembros del equipo teníamos una idea similar de las implicancias/alcance de cada ítem. Importante: estos puntos no los utilizamos para determinar la cantidad de trabajo de la iteración, sino simplemente como un mecanismo de doble validación interna.

Con este mecanismo en la iteración 7 planificamos unos 23 items pero completamos unos 18.

Antes de continuar debo decir que:

  • En la gran mayoría de equipos que he estado involucrado “con problemas de estimación”, resultó que en realidad era mucho más grave el problema de ejecución que el de estimación
  • Para equipos comprometidos, auto-organizados y trabajando en iteraciones cortas y con un esquema de continuous delivery no creo que valga la pena estimar “formalmente” con puntos/horas ni nada por el estilo más allá de la sensación del equipo

Dicho todo esto, mientras parte del equipo debatía sobre los ajustes al método de estimación/planificación, me puse a repasar el histórico de iteraciones en el Jira y confirmé mi sospecha viendo unos de los reportes provistos por la herramienta:

El nombre que la herramienta da a este gráfico es “Reporte de Velocidad” el mismo muestra en gris el plan del equipo al inicio de cada iteración y en verde lo efectivamente completado al final de la iteración. Se supone que a partir de este gráfico uno podría deducir una velocidad/capacidad del equipo que puede resultar útil a la hora de planificar.

Analizando este caso particular uno podría decir que este equipo puede entregar alrededor de 18 puntos por iteración. Sin embargo, ese no es el dato más importante de este gráfico para mí.

Lo que me resulta más relevante de este gráfico es la predecibilidad. Puede verse que independiente del número X planificado, este equipo entrega consistentemente con un desvió promedio de ~18 %, siendo este desvío en ocasiones positivo (se entrega más de lo planificado) y en ocasiones negativo (se entrega menos de lo planificado). Más aún, si eliminamos del cálculo la iteración 4 que fue particularmente corta en términos de días laborales (por feriados) y que tuvo una variación de equipo (se sumo una persona), el desvío promedio cae por debajo del 15%. Personalmente creo que es un grado de predecibilidad muy bueno, sobre todo si consideramos que el equipo tiene la capacidad (tanto a nivel proceso como de infraestructura) de entrega software potencialmente todos los días.

En general prefiero no hablar de la velocidad de un equipo pues creo que puede generar falsas expectativas por la sola terminología. Alguien fácilmente podría tentarse de pedirle al equipo que aumente su velocidad generando así presiones para el equipo. Y ni hablar de quienes pretenden comparar la velocidad de distintos equipos. Es por esto que prefiero no hablar de velocidad como una característica del equipo. Prefiero caracterizar al equipo por cuan predecible es.

TDDeando la arquitectura

El viernes pasado estuve dando una charla titulada así en el contexto del meetup online de ArqConf.

Había más de 370 registrados pero como suele ocurrir con los meetups gratuitos, rara vez se llega al 50 %. Cuando comencé a hablar había alrededor de 120 personas y me consta que luego se fueron sumando más pero no tengo presente cuántos.

Dado que la charla trataría sobre TDD, comencé haciendo una breve encuesta a los presentes sobre su conocimiento de TDD. Si bien más del 90 % conocía TDD, tan solo el ~17 % dijo utilizarlo a diario. Personalmente no me sorprende, porque según estudios formales que hemos realizado en los últimos años, el uso consistente de TDD ronda el 20%.

La presentación fue grabada y está disponible en YouTube y los slides están disponibles aquí.

Estos son algunos de los libros en los que me basé para armar la charla:

  • Growing object-oriented software guided by tests, de Freeman
  • Designing Software Architectures: A Practical Approach, de Cervantes y Kazman
  • Just Enough Software Architecture: A Risk-driven Approach, de Fairbanks
  • Building Evolutionary Architectures: Support Constant Change, de Ford
  • Specification by example, de Adzic

Agradezco a Gus Brey y demás organizadores por la invitación.

#HistoriaDeTrinchera: equipo completo

Al cabo de 6 iteraciones hemos completado el equipo. Resulta que al comenzar la sexta iteración se sumó el miembro faltante. Tenemos ahora cubiertos todos los roles/habilidades que consideramos necesarios para llevar adelante el proyecto y al mismo tiempo hemos alcanzado, a mi criterio (no estoy seguro que mis colegas compartan) el número máximo de personas que el equipo puede tener.

Nuestra expectativa es poder tener dentro del equipo todas las habilidades (y autorizaciones) para operar en un esquema de entrega continua dentro de ciertas restricciones organizacionales que por el momento no parecen estar abiertas a negociación (tema de otro post). En este sentido tenemos los siguientes roles/habilidades:

  • Developer
  • Tester
  • Diseñador de UI/UX
  • Facilitador
  • Product Owner
  • Infraestructura

Estos roles/habilidades son ocupados por personas que están en el día a día del proyecto y que como tal participan diariamente de las reuniones de sincronización (daily standups). Algunas aclaraciones que pueden resultar relevantes para el lector:

  • Developers: apuntamos a que puedan desarrollar una funcionalidad de punta a punta, lo cual implica tanto hacer tanto front (angular) como back (netCore) y tocar algunas cuestiones de infra como pipeline de CI/CD y generación de los correspondientes contenedores.
  • Testers: en el sentido de Agile, trabajan muy de la mano de la gente de negocio y con una visión funcional. Colaboran en la identificación/definición de casos y también en su automatización.
  • Diseñadores de UI/UX: diseñan/validan la experiencia del usuario y definen las pantallas. Trabajan muy de cerca con la gente del negocio y los usuarios. No codean, su trabajo llega hasta el maquetado con alguna herramienta tipo Miro, Marvel y Zeplin.
  • Facilitadores: llamados a veces Scrum Masters o coaches, ayudan a que el equipo mantenga el foco en el proceso y la entrega de valor, destraban impedimentos y mantienen los diálogos abiertos con otros equipos/áreas.
  • Product Owners: es gente con perfil de negocio que define el norte del producto, concentra los pedidos de los usuarios y los prioriza.
  • Infraestructura: más allá del control que tenga el equipo sobre la infraestructura es fundamental que el equipo entienda (y pueda definir y discutir) ciertas cuestiones de infraestructura porque pueden impactar en la implementación de algunas características/funcionalidades de la aplicación. En este caso particular, la organización tiene un área de DevOps con la cual nosotros como equipo de desarrollo trabajamos muy cerca en un ida y vuelta constante. Luego ese equipo de DevOps se encarga de la interacción con el resto de los sectores de infraestructura como seguridad, networking, etc.
    (este es un punto que puede resultar polémico y por ello le dedicaré un post aparte)

Todo este conjunto de roles/habilidades está repartido entre 12 personas, varias de ellas con asignación parcial (yo entre ellas). Para mi gusto ya son demasiadas personas y como algunos colegas coinciden con esto, es muy posible que en el corto/mediano plazo sumemos más gente y partamos el equipo en 2.

Próximas charlas sobre TDD

En las próxima semanas voy a estar participando de eventos virtuales con dos nuevas charlas sobre TDD que vengo trabajando desde hace un tiempo. Aún no tengo confirmadas la fechas (en cuanto las tenga las compartiré en redes) pero igualmente comparto la temática de las mismas.

TDD al banquillo

Test-Driven Development (TDD) es una práctica creada por Kent Beck a finales de los 90. En 20 años ha cosechado varios practicantes, algunos “fanáticos” y algunos detractores. Definitivamente es una práctica muy conocida pero…¿cuan utilizada es? ¿cuanta gente usa TDD?.

Al mismo tiempo los practicantes de TDD hablan de un conjunto de beneficios que ellos mismo han obtenido, pero en términos formales/científicos ¿cuales son la mejoras que nos trae el uso de TDD?

TDD claramente impacta en la forma en que trabajamos, pero ¿tiene algún impacto en el resultado de nuestro trabajo? ¿el software resultante es “mayor calidad”?

TDD surgió en el contexto de Extreme Programming junto con otras prácticas como Integración Continua, Pair-Programming y Collective Ownership ¿es posible utilizar TDD en forma “aislada” sin incorporar estas otras prácticas? ¿Es igual de efectivo?¿Cuales serían los riesgos?

En la charla planeo recorrer todos estos interrogantes e intentaré darles respuestas desde la formalidad de varios estudios, libros y obviamente también con un condimento de experiencia personal. No es una charla introductoria a TDD en el sentido que no voy a detenerme a explicar en detalle cómo es la dinámica de TDD, creo que ya hay bastante de eso. La charla está dirigida a gente que ya conoce la propuesta de TDD independientemente de que la este usando o no.

TDDeando la arquitectura

Los cimientos de TDD se establecieron a finales de los años 90 en el contexto de Extreme Programming pero podríamos decir que, recién en 2002, Kent Beck formalizo la técnica en su libro Test-Driven Development by Example. En ese libro Kent explica la técnica y muestra ejemplos trabajando a nivel de prueba unitaria, haciendo TDD sobre clases. Sin embargo el mindset de TDD puede aplicarse a otros niveles de abstracción como el mismo Beck lo ha admitido. En esta charla veremos cómo utilizar TDD para guiar la construcción de una arquitectura en forma emergente. Esta charla no es charla introductoria a TDD, voy explicar muy brevemente la dinámica propuesta por TDD pero no será ese el foco. La charla está dirigida a gente que ya conoce la propuesta de TDD independientemente de que la este usando o no.

Sobre por qué la programación orientada a objetos perdió relevancia

Erase una vez cuando se desarrollaban aplicaciones grandes, con decenas o incluso cientos de entidades. Era una época donde se esperaba que cada pieza de software viviera por muchos años y los ciclos de release podían extenderse por muchos meses excediendo incluso el año. Era una época donde la programación orientada a objetos y sobre todo un buen modelo de objetos podía marcar una diferencia relevante en la construcción, evolución y mantenimiento de una pieza de software[1].

Pero esa época ha quedado atrás, cada vez son más las demandas del negocio por dar respuestas rápidas a las situaciones cambiantes del mercado. Los ciclos de release se han acortado radicalmente y de mano de ello también alcance de cada aplicación. Los avances en virtualización y la popularización de los contenedores han habilitado el surgimiento de prácticas como infraestructura como código y continuous delivery. En este contexto, propuestas de diseño como la de microservicios han probado ser muy efectivas. Cada microservicio es una aplicación de un tamaño acotado, no hay un regla fija de esto pero se suele usar como referencia un bounded context [2]. En este hilo de razonamiento cada aplicación no tiene ya tantas entidades y tampoco se espera que un microservicio “viva por siempre”. Dado que los microservicios son relativamente pequeños no resulta impensado que luego de un tiempo sea reemplazado por una nueva versión que podría estar incluso desarrollada con una tecnología distinta.

Esto me lleva a pensar que en cierto modo la importancia de la programación orientada a objetos ha perdido cierta importancia relativa. En un punto cuestiones como la arquitectura de runtime (que impactan en la posibilidad de escalar) han tomado mayor relevancia (posiblemente porque la necesidad de escalar está mucho más presente ahora que hace 15 años).

Sin embargo estoy convencido que los principios de la orientación siguen completamente vigentes porque muchos de ellos son aplicables más allá de la programación. De hecho, los principios de diseño de los microservicios no son más que “refraseos” de los principios de orientación objetos. Un caso es el principio de Responsabilidad Única (Single Responsibility Principle): un objeto debe hacer UNA cosa y hacerla bien, eso mismo aplica a los microservicios. De hecho creo que perfectamente uno podría tomar los principios de orientación a objetos, aplicarlos al diseño de microservicios y luego programar internamente el microservicio utitilizando objetos pero de una forma más “procedural” utilizando alguna estrategia del Transaction Script. en lugar de hacer un Domain Model.

Tal vez este razonamiento genere alguna crítica y ante ello debo aclarar que he estado enseñando Programación Orientada a Objetos por más de 15 años y aún lo hago. Pero desde hace un tiempo vengo con la sensación de que en la medida en que podamos hacer aplicaciones/servicios pequeños, con pruebas automatizadas y prácticas de configuración management, la forma interna en que programemos las aplicación, resulta menos relevante.

[1] Posiblemente en algunos contextos esto siga ocurriendo
[2] Bounded Context es una idea desarrollada por Eric Evans en su libro Domain-Driven Design