Test Unitarios con Arduino y ArduinoUnit

Test Unitarios con Arduino y ArduinoUnit

tecnoingenia / septiembre 9, 2020

En esta entrada quiero hablar sobre un API para arduino que puede ser muy útil para aquellos que buscan mejorar la calidad de su código. La descubrí hace poco pero me pareció muy interesante.
Se trata de la librería ArduinoUnit con la que podemos probar nuestros bloques de código, aquí quiero hablarle de las funciones más básicas y de cómo lo he estado aprovechando en mis códigos, pero primero…

¿Qué es un test unitario?

En muy resumidas cuentas un test unitario busca probar las funciones que hemos creado de forma aislada del resto del código, el objetivo es detectar errores desde faces muy tempranas del desarrollo, comprobando que la lógica de cada función haga su trabajo independientemente de las demás.
Con esta pequeña introducción puedo comenzar a hablar de ArduinoUnit, este API nos sirve como una herramienta para poder realizar nuestras pruebas, usando el mismo arduino y su puerto serie como una interfaz con el developer.
Vamos a ilustrar esto con un ejemplo muy sencillo, haremos un test unitario en un programa típico en el que se hace parpadear un led. Este solo cuenta con una función llamada toggleLed, que lo único que hace es cambiar el estado del led entre LOW y HIGH.
/*Archivo: blink.ino*/
#define PIN_LED 13
#define DELAY 500

#define UNIT_TEST

#ifdef UNIT_TEST
  #include <ArduinoUnit.h>
#endif

/*STATIC FUNCTIONS*****************************/
static void toggleLed(void);

/* static variables****************************/
static boolean ledStatus = LOW;
static unsigned long lastBlinkTime = 0;

void setup() {
  Serial.begin(9600);
  pinMode(PIN_LED, OUTPUT);
}

void loop() {
#ifndef UNIT_TEST  
  if(millis() - lastBlinkTime >= DELAY)
  {
    toggleLed();
    digitalWrite(PIN_LED, ledStatus);

    lastBlinkTime = millis();
  }
#else
    Test::run();
#endif
}


/*****************************************
 * Función: toggleLed
 * Descripción: Esta función cambia el estado del led (ledStatus),
 * es decir si es LOW lo cambia a HIGH y viceversa.
 *****************************************/
void toggleLed(void)
{
  ledStatus =  !ledStatus;
}
Y en el siguiente archivo programamos las pruebas…
#line 2 "unitTest.ino"
#ifdef UNIT_TEST

test(toggleLed)
{
  ledStatus = LOW;              /*PRECONDICIÓN*/
  toggleLed();                  /*EJECUTAR FUNCIÓN*/
  assertEqual(ledStatus, HIGH); /*EVALUAR CON ASSERT*/

  ledStatus = HIGH;             /*PRECONDICIÓN*/
  toggleLed();                  /*EJECUTAR FUNCIÓN*/
  assertEqual(ledStatus, LOW);  /*EVALUAR CON ASSERT*/
}

#endif
Salida «Test Passed»
Como podrán observar por medio del puerto serie nos muestra un listado de las pruebas realizadas y si pasaron o fallaron. En este caso solo se realizó una prueba el «Test toggleLed».
Los pasos importantes para cualquier test son, primero hay que ejecutar el método «Test::run();» en nuestro Loop principal para que se ejecuten nuestras pruebas.
Segundo, definir nuestros tests, normalmente se identifican con el nombre de la función a evaluar, primero se setean las precondiciones necesarias, luego se ejecuta la función a evaluar y finalmente se hace un assertion para realizar la validación. En este ejemplo se usó el assert más común que es assertEqual, lo que hace es evaluar si los dos parámetros entre paréntesis son iguales, existen muchas otras formas de evaluar por desigualdad, o mayor y menor qué, etc. Lo pueden revisar en la documentación de la librería.
Adicionalmente yo definí UNIT_TEST para poder elegir si quiero ejecutar el código normalmente o si quiero ejecutar las pruebas.
veamos ahora que sucede si intencionalmente generamos un error
#line 2 "unitTest.ino"
#ifdef UNIT_TEST

test(toggleLed)
{
  ledStatus = LOW;
  toggleLed();
  assertEqual(ledStatus, LOW); /*ERROR GARRAFAL ¿Qué estás haciendo boludo?*/

  ledStatus = HIGH;
  toggleLed();
  assertEqual(ledStatus, LOW);
}

#endif
Salida «Test Failed»
Como podemos ver nos marca el assertion fallido y nos indica en que linea se encuentra y el identificador del test. Además como información adicional nos indica los valores comparados que nos es muy útil para identificar el error.
Es importante incluir 
#line 2 "nombreDeTuArchivo.ino"
En la primera linea del archivo en que se encuentren nuestros tests, para que nos devuelva los números de linea correctos.
Realmente está es una introducción muy básica pero creo que puede ser muy útil para iniciar con tus pruebas e incrementar la calidad de tu código.
Ahora, el ideal de estás pruebas es que puedan cubrir la totalidad de los casos o condiciones de nuestro código por el momento ArduinoUnit no nos entrega métricas sobre la cobertura. Y otro ideal es que las pruebas se realizaran sobre los módulos o librerías por separado y aislar lo más posible cada bloque de código.
Para aislar nuestras funciones puede complicarse cuando llamamos funciones dentro de otras y el encontrar un error puede llevar más tiempo de lo pensado, es aquí cuando entra otro concepto de unit Test llamado MOCK o Mocking, un MOCK básicamente es una forma de simular el comportamiento de un método o función, para así no tener que llamar a la función original y esta no interfiera en nuestro test. 
El MOCK toma el identificador de la función y nos puede entregar información de si fue llamada como lo esperábamos y nos permite configurar las entradas y salida deseada.
Justamente estuve trabajando en los test unitarios de mi librería SerialCenter, para poder evaluar lo que pasaba con el puerto serie diseñé una librería para generar un MOCK del puerto serie, la llamé StreamMOCK me parece algo interesante de revisar para quien necesite hacer pruebas en puerto Serie además subí las acualizaciones de SerialCenter y sus pruebas unitarias a mi repositorio de github por si alguien quiere darle un vistaso.

 

Para tenerminar me gustaría comentar que he visto algunos comentarios negativos acerca de que es necesario tener hardware (es decir un arduino) para usar esta librería, en mi opinión sigue siendo muy útil a pesar de sus limitaciones, además esto solo nos da la oportunidad de que la comunidad siga aportando mejoras.

Leave a Reply

TecnoIngenia | Blog personal -israel.barreras@tecnoingenia.com