Hacia un framework de desarrollo guiado por pruebas para Vala - cuarta parte - Quien pone a prueba el probador?

Posted on Thu 04 February 2016 in Vala

Después de una breva pausa para trabajar en uno de mis otros proyectos (un grupo de Rock 'n Roll) y terminar con la instalación de Jenkins, he vuelto a trabajar en el proyecto ya oficialmente llamado Valadate.

Como he mencionado antes, hubo algunos intentos iniciales de desarrollar un framework de TDD para Vala, el Valadate siendo lo más extenso de ellos. Después de pensarlo bien, y un revisión de la base de código existente, decidí que la propuesta más practica seria asumir el papel de mantenedor de proyecto y refundirlo como sea necesario para cumplir los nuevos requisitos que se han sido recopilados.

Actualmente, el paquete de Valadate dispone de varias clases de utilidad para las tareas como pruebas asincrónicas y directorios temporales además de un Test Runner de la linea de comando. El proceso para escribir pruebas es crear una implementación concreta de la interfaz de Valadate Fixture con las pruebas siendo los métodos cuyos nombres empiezan con test_. La prueba está compilado en un binario (una biblioteca compartida) que es ejecutado por el Test Runner. El descubrimiento de pruebas se hace mediante cargar los archivos de .vapi y .gir generado por Vala cuando el binario sea compilado. El sistema de construir es Waf, pero para revisar el código, lo porte a autotools, un sistema de construir que me resulta más cómodo.

El código se compila, pero ha sufrido un poco de pudrición, con varios avisos de deprecación, especialmente las pruebas asincrónicas. El framework si mismo es muy flaco y usar las clases de GLib Test y TestSuite para agrupar y ejecutar las pruebas que se encuentran dentro del binario. En suma no haya más de 1000 lineas de código fuente (SLOC en ingles) en el proyecto. Aunque haya algunas ideas interesantes en el código actual, he decidido que la mejor propuesta seria empezar de nuevo y integrar lo que sea útil y mandar los restos al cielo || infierno binario.

Ya que hay un repositorio para Valadate armado y actualizado para ser construido con autotools, voy a utilizarlo como el master de lo que vamos a derivar las varias ramas de desarrollo, siguiendo la practica amplia usada de "GitHub Flow", un proceso de gestión de repositorios que encarna los principios de Integración Continua. En una palabra, se implica seis pasos discretos:

  1. Crear una rama para desarrollar una función nueva
  2. Agregar los commits a la rama
  3. Abrir peticiones de Pull
  4. Comentar y revisar el código
  5. Desplegar
  6. Unir

El principio fundamental (o "la única regla" como se llama el GitHub) es que la rama master está siempre lista para ser desplegada - que el caso de una herramienta como Valadate implica que se puede tirar, compilar y ejecutar en cualquier momento. Así, mientras que la rama master existente de Valadate no es exactamente lista para producción, está en el estado donde la Fundacion de Yorba dejo de mantenerlo. Por lo menos no da una linea de saque de donde podemos empezar y un poquito de continuidad con el proyecto original, aunque solo sea para dar crédito a los desarrolladores originales por su afán.

Estamos listos para bifurcar nuestra versión nueva, que vamos a llamarla? El sistema de uso comun es el Versionamiento Semántico lo que sigue la convención de MAJOR.MINOR.PATCH.

  • version MAJOR cuando hay cambios incompatibles a la API
  • version MINOR cuando hay funciones nuevas agregadas en una manera compatible con versiones anteriores
  • version PATCH cuando hay correcciones de errores compatibles con versiones anteriores

El ultimo lanzamiento de Valadate fue 0.1.1 y no está totalmente claro si estaban siguiendo el esquema del Versionamiento Semántico. Hay distinto números de versión para las API y SO los cual es posible no vamos a usar en nuestro primer lanzamiento. Para que sea sencillo, voy a utilizar el numero de versión original como el punto de partida. Como vamos a hacer algunos cambios sustanciales que van a romperlo en completo la API de versión 0, deberíamos incrementarlo a uno. Dado que vamos a empezar desde cero, la versión MINOR se revertirá a 0 también. Así que el nombre de la rama de nuestra nueva implementación sera 1.0.0.

Buenaso. Marcamos el numero!

$ git checkout -b version-1.0.0

El repositorio local ya tiene una rama llamada version-1.0.0 la que nos permite de dar un repaso general del código sin afectar la rama "desplegable" de master. Ya que vamos a romper mas cosas que un stoner en una tienda de pipas de agua, bien podemos reorganizar la disposición de los archivo en algo más convencional y disponer con el sistema de Waf en total.

La estructura nueva de nuestro repositorio ya parece así:

  • valadate
    • libvaladate
    • src
    • tests
      • libvaladate
      • src

Esta estructura es un modelo bastante común para los proyectos de tamaño medio a grande, esencialmente se recrear el árbol de código fuente dentro del directorio de tests. Así se resulta más fácil encontrar las pruebas particulares y significa que las pruebas de integración van a siguir el mismo modelo básico a la hora de ser compilado. Con los proyectos más pequeños, se puede conseguir el mismo resultado con un directorio de test nomas - con las pocas lineas de código fuente que tiene Valadate es posible que se caben dentro de un archivo solo! Dado que esperamos que el proyecto se crezca considerablemente, especialmente cuando empezamos de agregar funciones complejas como pruebas de BDD y una interfaz gráfica tal como varios niveles de pruebas de pruebas, deberíamos empezar con una estructura más escalable.

Dale, por fin estamos listo para empezar con las pruebas. Dado que este es un Framework de Pruebas, nos enfrenta una situación de huevo o la gallina - que framework vamos a usar para probar nuestro framework? En este caso, la solución es sencillo, tenemos el suite de GLib Test a nuestra disposición lo cual podemos utilizar para escribir las pruebas de base que van a guiar el diseño del framework. Una vez que esas pruebas se aproben, podemos pasar de usar Valadate para probar si mismo cuando se agregan nuevas funciones más complejas como Gherkin/Cucumber. Al final, podemos usar esas funciones para pruebas aun más complejas tal como las de validación de usuario y integración para el proyecto en general. El proceso es iterativo y en cascada, en el sentido que cuando las funciones de un nivel sean suficientemente probadas, serian disponibles para el nivel siguiente de pruebas. Puedes pensar que es como una cebolla, si quieres, o una serie de cascadas pero la imagen mental que tengo yo es más como esta:

Pero así soy yo. Usas cualquier metáfora que quieres, al fin y al cabo es tu mente.

Así empezamos utilizando el básico framework de GLib Testing. Osea, de veras el Glib Testing Framework tiene mucha potencia y al primero fue diseñado según la interfaz de xUnit. Es bastante fácil usarlo, como este ejemplo del Wiki de Gnome Vala muestra:

void add_foo_tests () {
    Test.add_func ("/vala/test", () => {
        assert ("foo" + "bar" == "foobar");
    });
}

void main (string[] args) {
    Test.init (ref args);
    add_foo_tests ();
    Test.run ();
}

Además, tiene las utilidades de gtester y gtester-report las cuales son integrados bien con las cadenas de herramienta existentes y son capaces de producir los resultados de las pruebas en varios formatos.

Las desventajas principales del GLib Testing Framework, y por lo tanto la necesidad de Valadate en absoluto, son:

  • No está particularmente orientado hacia objetos - la clases de base son todas [Compact] y no heredan de una clase común. Así resulta difícil extenderlas en Vala.
  • Las funciones de los informes de pruebas necesitan ser configuradas para producir algo útil, incluso varios 'drivers' o scripts de shell para el proceso anterior.
  • No está bien documentado
  • No escalar bien para los proyectos grandes o para el diseño guiado por comportamiento.
  • Es verboso y difícil de leer.

La mayoría de esas limitaciones se pueden resolver de una manera u otro, así debería bastar como un punto de partida. Si sigamos los principios de diseño guiado por pruebas debería ser obvio cuando tendremos construir algo más poderos o flexible.

Cuales pruebas y funciones escribamos en primero? Pues, eso se determina mediante los requisitos que ya recompilamos y como los hemos priorizado. Una de las muchas ventajas de tener una esposa que es una CTO para un fundación que va desarrollando software libre de tenencia de la tierra es que yo puedo experimentar vicariamente como ella gestionar el flujo de trabajo de su equipo y las herramientas que usan. Una de las herramientas que recién empezaron de utilizar para la gestión de los proyectos es Waffle, la que integrarse bien con los asuntos de GitHub y las peticiones de Pull. Waffle es el paso más allá de la placa de Trello que usaba para recopilar los requisitos para Valadate. Waffle permite que cualquiera agrega una petición de funciones o mandar un resumen de error al Backlog o mediante la placa de Waffle para el proyecto o por crear un nuevo asunto en la pagina de GitHub. El ultimo es la manera más sencilla como no hay que acceder a Waffle en absoluto.

Una de las filosofías de código abierto de mi esposa es no basta lanzar el código de fuente. Un proyecto de código abierto verdadero es también desarrollado en una manera abierta - lo que implica que haya una historia registrada de todas las decisiones, porque y por quien, y todos los asuntos y peticiones de Pull sean revisados y cuando cumplan los requisitos del proyecto (es decir los del usuario) son arreglados o unidos, a pesar de la fuente. Los repositorios públicos son, por lo menos espejos sino la versión corriente del master y las ramas, no solamente una instantánea estática del ultimo lanzamiento.

Tomar una posición <> es algo esencial en levantar una comunidad fuerte y pluralista de usuarios centrada en el producto. La Sara Sharp, una colaboradora de largo plazo, ha escrito extensamente sobre este tema en sublog. Una de las cosas que voy a tomar la oportunidad de realizar ahora es un Código de Conducta. No voy a hablar de los pros y contras de tener un Código de Conducta - no veo ningún contra! Así que, como dice la Sarah en su blog -

No escribimos acuerdos legales sin la ayuda de expertos. No escribimos nuestras propias licencias de código abierto. No enrollamos nuestra propia criptografía sin el consejo de expertos. No debemos enrollar nuestro propio Código de Conducta.1

Tomando esto en cuenta, he inscrito el proyecto en el Open Code of Conduct, lo que está usado por GitHub y inspirado por los códigos de conducta y las declaraciones de la diversidad de proyectos como Django, Python y Ubuntu. Vale la pena leerlo, pero ahí va mi resumen - "no seas pelotudo" - y me puedes citar en Twitter.

Dale, ya está para este post. De prontito voy a publicar la parte 5 cuando voy a trazar el roadmap del producto para el primer lanzamiento y contestar la pregunta de "cuando vamos a saber si las pruebas bastaran" con los informes de cobertura. Gracias por leer y si tienes algo de decir, por fa dilo!