No ejecute pruebas unitarias en el dispositivo o emulador Arduino
El caso contra el microcontrolador Dispositivo / Emulador / Pruebas basadas en Sim
Se discute mucho sobre lo que significa la prueba unitaria y realmente no estoy tratando de discutir aquí. Esta publicación no le
dice que evite todas las pruebas prácticas en su hardware objetivo final. Estoy tratando de hacer un punto sobre la optimización de su ciclo de retroalimentación de desarrollo eliminando su hardware objetivo de sus pruebas más mundanas y frecuentes. Se supone que las unidades bajo prueba son mucho más pequeñas que todo el proyecto.
El propósito de las pruebas unitarias es probar la calidad de su propio código. Las pruebas unitarias generalmente nunca deben probar la funcionalidad de factores fuera de su control.
Piénselo de esta manera: incluso si tuviera que probar la funcionalidad de la biblioteca Arduino, el hardware del microcontrolador o un emulador, es absolutamente imposible que dichos resultados de prueba le digan algo sobre la calidad de su propio trabajo. Por lo tanto, es mucho más valioso y eficiente escribir pruebas unitarias que no se ejecutan en el dispositivo (o emulador) de destino.
Las pruebas frecuentes en el hardware de destino tienen un ciclo extremadamente lento:
- Ajusta tu código
- Compila y carga en el dispositivo Arduino
- Observa el comportamiento y adivina si tu código está haciendo lo que esperas
- Repetir
El paso 3 es particularmente desagradable si espera recibir mensajes de diagnóstico a través del puerto serie, pero su proyecto en sí necesita usar el único puerto serie de hardware de su Arduino. Si estaba pensando que la biblioteca SoftwareSerial podría ayudar, debe saber que al hacerlo es probable que interrumpa cualquier funcionalidad que requiera una sincronización precisa como generar otras señales al mismo tiempo. Este problema me ha pasado.
Una vez más, si tuviera que probar su boceto utilizando un emulador y sus rutinas de tiempo crítico funcionaran perfectamente hasta que lo cargó en el Arduino real, entonces la única lección que aprenderá es que el emulador tiene fallas, y saber esto todavía no revela nada sobre la calidad de su propio trabajo.
Si es una tontería probar en el dispositivo o emulador, ¿ qué debo hacer?
Probablemente estés usando una computadora para trabajar en tu proyecto Arduino. Esa computadora es un orden de magnitud más rápida que el microcontrolador. Escriba las pruebas para construir y ejecutar en su computadora .
Recuerde, se debe suponer que el comportamiento de la biblioteca y el microcontrolador Arduino es correcto o, al menos, consistentemente incorrecto .
Cuando sus pruebas producen resultados contrarios a sus expectativas, es probable que tenga un error en el código que se probó. Si el resultado de su prueba coincide con sus expectativas, pero el programa no se comporta correctamente cuando lo carga en Arduino, entonces sabe que sus pruebas se basaron en suposiciones incorrectas y es probable que tenga una prueba defectuosa. En cualquier caso, se le proporcionará información real sobre cuáles deberían ser sus próximos cambios de código. La calidad de sus comentarios ha mejorado de " algo está roto" a "este código específico está roto" .
Cómo construir y ejecutar pruebas en tu PC
Lo primero que debe hacer es identificar sus objetivos de prueba . Piense en qué partes de su propio código desea probar y luego asegúrese de construir su programa de tal manera que pueda aislar partes discretas para la prueba.
Si las partes que desea probar requieren funciones de Arduino, deberá proporcionar reemplazos de maquetas en su programa de prueba. Esto es mucho menos trabajo de lo que parece. Sus maquetas no tienen que hacer nada más que proporcionar entradas y salidas predecibles para sus pruebas.
Cualquiera de sus propios códigos que tiene la intención de probar debe existir en archivos de origen que no sean el bosquejo .pde. No se preocupe, su boceto aún se compilará incluso con algún código fuente fuera del boceto. Cuando realmente te pones a ello, poco más que el punto de entrada normal de tu programa debe definirse en el archivo de boceto.
¡Todo lo que queda es escribir las pruebas reales y luego compilarlas usando su compilador C ++ favorito! Esto probablemente se ilustra mejor con un ejemplo del mundo real.
Un ejemplo de trabajo real
Uno de mis proyectos favoritos que se encuentran aquí tiene algunas pruebas simples que se ejecutan en la PC. Para el envío de esta respuesta, voy a repasar cómo simulé algunas de las funciones de la biblioteca Arduino y las pruebas que escribí para probar esas simulaciones. Esto no es contrario a lo que dije antes acerca de no probar el código de otras personas porque yo fui quien escribió las maquetas. Quería estar muy seguro de que mis maquetas eran correctas.
Fuente de mock_arduino.cpp, que contiene código que duplica algunas funciones de soporte proporcionadas por la biblioteca Arduino:
#include <sys/timeb.h>
#include "mock_arduino.h"
timeb t_start;
unsigned long millis() {
timeb t_now;
ftime(&t_now);
return (t_now.time - t_start.time) * 1000 + (t_now.millitm - t_start.millitm);
}
void delay( unsigned long ms ) {
unsigned long start = millis();
while(millis() - start < ms){}
}
void initialize_mock_arduino() {
ftime(&t_start);
}
Utilizo la siguiente maqueta para producir una salida legible cuando mi código escribe datos binarios en el dispositivo serial de hardware.
fake_serial.h
#include <iostream>
class FakeSerial {
public:
void begin(unsigned long);
void end();
size_t write(const unsigned char*, size_t);
};
extern FakeSerial Serial;
fake_serial.cpp
#include <cstring>
#include <iostream>
#include <iomanip>
#include "fake_serial.h"
void FakeSerial::begin(unsigned long speed) {
return;
}
void FakeSerial::end() {
return;
}
size_t FakeSerial::write( const unsigned char buf[], size_t size ) {
using namespace std;
ios_base::fmtflags oldFlags = cout.flags();
streamsize oldPrec = cout.precision();
char oldFill = cout.fill();
cout << "Serial::write: ";
cout << internal << setfill('0');
for( unsigned int i = 0; i < size; i++ ){
cout << setw(2) << hex << (unsigned int)buf[i] << " ";
}
cout << endl;
cout.flags(oldFlags);
cout.precision(oldPrec);
cout.fill(oldFill);
return size;
}
FakeSerial Serial;
y finalmente, el programa de prueba real:
#include "mock_arduino.h"
using namespace std;
void millis_test() {
unsigned long start = millis();
cout << "millis() test start: " << start << endl;
while( millis() - start < 10000 ) {
cout << millis() << endl;
sleep(1);
}
unsigned long end = millis();
cout << "End of test - duration: " << end - start << "ms" << endl;
}
void delay_test() {
unsigned long start = millis();
cout << "delay() test start: " << start << endl;
while( millis() - start < 10000 ) {
cout << millis() << endl;
delay(250);
}
unsigned long end = millis();
cout << "End of test - duration: " << end - start << "ms" << endl;
}
void run_tests() {
millis_test();
delay_test();
}
int main(int argc, char **argv){
initialize_mock_arduino();
run_tests();
}
Esta publicación es lo suficientemente larga, así que consulte mi proyecto en GitHub para ver más casos de prueba en acción. Mantengo mis trabajos en curso en ramas distintas a la maestra, así que verifique esas ramas para pruebas adicionales también.
Elegí escribir mis propias rutinas de prueba livianas, pero también hay disponibles marcos de prueba de unidad más robustos como CppUnit.