Estoy buscando algunas estrategias de mejores prácticas para el código de prueba de unidad escrito para el sistema embebido. Por sistema incrustado, me refiero a códigos como controladores de dispositivos, controladores ISR, etc., cosas que están bastante cerca del metal.
La mayoría de las pruebas unitarias no son posibles sin probarlo en el hardware con la ayuda de un ICE. A veces, la unidad integrada también debe conectarse a otros estímulos, como interruptores mecánicos, motores paso a paso y bombillas. Esto generalmente ocurre de forma manual, la automatización sería excelente, pero difícil y costosa de lograr.
Actualizar
Encontré un marco de prueba C que parece tener bastante éxito en la prueba de proyectos integrados. Utiliza las ideas de burlarse de hardware. Echa un vistazo a Unity , CMock y posiblemente Ceedling .
Actualización 06Jul2016
Se encontró con cmocka - parece ser trabajado más activamente en.
fuente
Respuestas:
Me alejaría de las dependencias de hardware en el primer paso posible, y construiría el sistema en arneses de prueba / emulación de software, permitiendo todo tipo de marcos de prueba. A menudo, mi PC de desarrollo se usaba para probar hasta el 95% o más del sistema completo. El costo de la sobrecarga adicional (otra capa de abstracción) fue fácilmente recuperado por el código más limpio generado como resultado de esa abstracción.
La prueba de las partes verdaderamente desnudas de metal de un sistema embebido suele ser una aplicación separada (¿Prueba de unidad?) Que afecta el firmware mucho más allá de lo que las aplicaciones pueden esperar lograr. La automatización se puede hacer a un costo, pero no es típico.
A menos que tenga el presupuesto para construir un arnés de hardware de prueba de unidad que incluya ICE completo. Esto está absolutamente bien ya que generalmente las pruebas funcionales son pequeñas.
fuente
Una herramienta necesaria para desarrollar es un inyector de señal. El sistema integrado tendrá alguna forma de interactuar con un sistema host (generalmente a través de un puerto serie reservado para la depuración). Use esto para enviar datos de prueba (la mejor opción es un formato ascii conciso para que los humanos también lo simulen fácilmente).
Estoy completamente en desacuerdo con esta parte de su pregunta: "la automatización sería excelente, pero difícil y costosa de lograr".
Usando TeraTerm como un inyector de señal de puerto serie y escribiendo algunas macros de TeraTerm (toma aproximadamente 20 minutos), hay un enorme conjunto de pruebas automatizadas que se pueden ejecutar en cualquier parte de un sistema embebido, ya sea capa de controlador, O / S, capa 4-5, etc. TeraTerm: http://en.sourceforge.jp/projects/ttssh2/
Si el puerto serie no está disponible en el sistema incorporado, utilice una herramienta de hardware para convertir los datos del puerto USB / serie en señales digitales (también de bajo costo y fácil de lograr). Mientras lees esto, estoy usando una placa de microcontrolador de $ 30 (UBW: http://www.schmalzhaus.com/UBW32/ ) para probar un sistema embebido para producción, inyectando estímulo a través de macros TeraTerm que se envía por USB / serial a el microcontrolador, que ejecuta firmware modificado que ejercita las entradas digitales y monitorea las salidas digitales del sistema embebido objetivo. Junto con esto, desarrollamos un script de Python (utiliza pyserial y pexpect) para automatizar la inyección de datos y la validación de datos. Nada de eso es difícil y nada es caro. En mi experiencia, los gerentes gastan grandes sumas de dinero (como equipo de prueba de $ 30,000) cuando el equipo de prueba no tiene experiencia y no puede concebir estas soluciones fáciles; desafortunadamente, el equipo de hierro grande de uso general a menudo no incluye los casos de prueba que capturan el peor momento / etc del sistema de destino. Por lo tanto, el método económico es preferible para la cobertura de la prueba. Por extraño que parezca.
fuente
Este es un problema muy difícil.
De hecho, he diseñado un arnés de prueba de unidad para un sistema integrado, que permitiría simular eventos / interrupciones de hardware y controlar el momento de la ejecución (para garantizar que cubramos todos los entrelazamientos posibles debido a la concurrencia), y se necesitó un equipo de programadores de más de 2 años para implementarlo y ponerlo a trabajar. Ese proyecto es un desarrollo patentado, pero un proyecto similar (de diseño más simple) está disponible aquí .
Entonces sí, la automatización sería genial. Sí, es muy difícil y costoso de lograr. Sí, a veces tienes que hacer eso. Sin embargo, rara vez, en mi experiencia, en la mayoría de los casos, es más rápido y más barato usar los motores paso a paso y las bombillas y hacer que todo funcione manualmente.
fuente
Editar: mi respuesta es cercana a la de Mattnz, creo ...
Quiero relacionar este problema con otros, todas las pruebas que dependen de algo externo a su código (como el reloj del sistema, un sistema de archivos persistente o una base de datos, contactar un servicio web externo ...). Sugiero la misma política para todos ellos, aislar los dos niveles en dos capas de código.
Probar una sola operación externa
Es posible que desee probar físicamente cada operación. Verifique que el reloj del sistema proporcione la hora correcta, verifique que un archivo realmente recuerde lo que se ha escrito, verifique que un dispositivo reciba una sola operación ...
Estas pruebas:
Probar la lógica (código, algoritmo) que une las operaciones externas
Al tener una capa de código para realizar las operaciones externas reales, al ocultarlas detrás de una interfaz que puede burlarse fácilmente, su lógica ya no depende de los dispositivos físicos reales ...
Puede probar simplemente, como cualquier proyecto normal, ya no está en un código incrustado difícil de probar .
fuente
Los simuladores de CPU integrados generalmente se pueden programar para simular también hardware. Todas las tecnologías de virtualización que no sean Xen hacen eso. Pero debe escribir un código que pretenda tener algunos registros en alguna dirección física o, en x86, una dirección en el bus de E / S, y luego debe responder a las lecturas y escrituras en estas direcciones como si su software fuera físico chip cuyo control y registros de estado se estaban accediendo.
Si desea hacer esto, sugeriría modificar QEMU. Pero no sería fácil. Este tipo de cosas generalmente solo se hace cuando está diseñando un chip personalizado con un microcontrolador y algunos otros núcleos para su E / S.
El sistema de desarrollo vendido por ARM Holdings ofrece esto y es más fácil trabajar con él que piratear QEMU, pero es muy costoso.
Hay varios emuladores ARM de código abierto que ejecutan una sola subrutina, que puede llamar a otras subrutinas, que puede usar para depurar y ajustar el rendimiento de las subrutinas que no dependen del acceso al hardware. Utilicé uno de estos con gran éxito para optimizar un cifrador AES para ARM7TDMI.
Puede escribir un arnés de prueba de unidad simple en C o C ++, vincular la clase o subrutina bajo prueba con él y luego ejecutarlo en el simulador.
He estado reflexionando sobre un problema similar durante años, cómo hacer una prueba unitaria del código del kernel de Linux o Mac OS X. Debería ser posible, pero nunca lo he intentado. Una posiblemente es construir un núcleo completo en lugar de probar su código de forma aislada, con el marco de prueba de la unidad vinculado directamente a su núcleo. Luego dispararía las pruebas unitarias desde algún tipo de interfaz externa.
Tal vez sería más productivo usar una herramienta de cobertura de código, luego probar su firmware como un paquete completo a través de su interfaz externa. La herramienta de cobertura encontraría rutas de código que aún no se habían probado, por lo que podría agregar pruebas externas adicionales en un intento de obtener más cobertura.
fuente
Al igual que con TDD no incrustado, los objetos simulados son definitivamente tu amigo.
Mantenga la interfaz de su hardware subyacente limpia y simple para que todo lo que esté por encima del nivel más bajo se pueda burlar y le resulte mucho más fácil: si diseña su aplicación integrada teniendo en cuenta la capacidad de prueba, las pruebas siempre serán mucho más fluidas .
Además, el hecho de que no pueda realizar las pruebas en línea hasta bastante tarde en el proyecto no significa que no deba preparar un conjunto de pruebas en línea también.
Estos (inicialmente) solo deben probar los bits que no se pudieron probar fuera de línea. Claro, no es TDD (ya que está creando las pruebas por adelantado), pero su desarrollo de TDD fuera de línea debería darle una buena idea de cómo debe verse su interfaz de hardware y, por lo tanto, qué pruebas en línea debe realizar.
Además, si el desarrollo en línea cuesta mucho más que el desarrollo fuera de línea (como lo hace donde trabajo), podría ahorrarle mucho tiempo en línea al tener un conjunto de pruebas bien entendidas para ejecutar.
fuente
En el desarrollo integrado, a menudo realiza escaneos de límites para verificar que toda la aplicación (incluido el hardware) funcione. Consulte también JTAG para la depuración del sistema.
La prueba de rutinas de software puro sin enlace al hardware puede realizarse mediante un marco de prueba de unidad C estándar como Check . Pero tenga cuidado con las limitaciones de memoria (especialmente el espacio de pila, etc. en dispositivos pequeños). ¡Conozca sus contratos! También puede intentar abstraer las rutinas de software del hardware para obtener una mayor cobertura de prueba, pero esto generalmente es costoso en términos de rendimiento en dispositivos integrados como pequeños PIC o AVR. Sin embargo, puede simular puertos de hardware para lograr una mayor cobertura (y, por supuesto, también puede probar ese simulacro).
También puede intentar usar emuladores para los simuladores de circuitos o chips, pero este tipo de herramientas son caras (especialmente en combinación) y complicadas.
fuente