Introduccion
Todos conocen los beneficios de las pruebas unitarias. En primer lugar, escribir pruebas al mismo tiempo que el código le permite detectar errores antes y no perder tiempo posteriormente en la depuración compleja que consume mucho tiempo. En el caso del desarrollo integrado, las pruebas unitarias tienen características relacionadas, en primer lugar, con el hecho de que el código se ejecuta en algún lugar profundo de las entrañas del dispositivo y es bastante difícil interactuar con él, y en segundo lugar, el código está fuertemente vinculado al hardware de destino. .
Si hay fragmentos en el proyecto que no dependen del hardware y al mismo tiempo implementan una lógica bastante compleja, para ellos el uso de pruebas unitarias dará el mayor beneficio. Por ejemplo, puede ser la implementación de un protocolo de transferencia de datos, varios cálculos o una máquina de estado de control.
Hay tres formas de ejecutar pruebas unitarias para plataformas integradas:
- Inicie directamente en la plataforma de destino. En este caso, puedes trabajar con el equipo del dispositivo, y el código funcionará exactamente igual que en condiciones de combate. Sin embargo, para las pruebas necesitará acceso físico al dispositivo. Además, el ciclo de prueba será bastante largo debido a la necesidad de descargar constantemente el código al dispositivo.
- Corriendo en un emulador. Este método es bueno principalmente porque le permite trabajar incluso cuando la plataforma de destino no está disponible (por ejemplo, porque aún no se ha hecho). Las desventajas son la precisión limitada en la reproducción del comportamiento del hierro (y el mundo circundante), así como la dificultad de crear dicho emulador.
- Ejecutando en la máquina host (localmente). No funcionará con el equipo (puede usar trozos de prueba en su lugar), pero las pruebas comenzarán y funcionarán rápidamente, y no necesita acceso al dispositivo de destino. Un buen ejemplo para usar este método es probar la implementación de un algoritmo computacional en el microcontrolador, que en sí mismo no depende del hardware, sino que usa los datos del sensor del dispositivo. Probar un algoritmo con una fuente de datos real será muy inconveniente, es mucho mejor registrar estas mediciones una vez y ejecutar pruebas que ya estén en los datos almacenados. Este script ejecutará pruebas localmente y se discutirá más adelante.
Esta publicación proporciona una forma de configurar pruebas unitarias en el entorno STM32CubeIDE, basado en Eclipse y destinado al desarrollo de controladores de la familia STM32. El lenguaje de desarrollo es C, pero las pruebas mismas están escritas en C ++. Las pruebas se ejecutarán en una máquina host de Windows con Cygwin. Como marco de prueba, se utiliza Google Test. Los resultados se mostrarán en una ventana de complemento especial para pruebas unitarias, y se pueden iniciar con un botón del proyecto para STM32:

El método descrito es adecuado para otros entornos de desarrollo basados en Eclipse, a menos que, por supuesto, los buenos fabricantes los hayan cortado demasiado por conveniencia de los desarrolladores. Este método también funcionará con CubeIDE en Linux, sin la necesidad de molestarse con Cygwin.
Necesitarás
- Cygwin 3.0.7 x86 (dado que las pruebas son para un microcontrolador de 32 bits, también utilizaremos un entorno de 32 bits en una plataforma de 64 bits)
- STM32CubeIDE 1.0.2 para Windows.
- Google Test Framework 1.8.1
Instale Cygwin y STM32CubeIDE
Cygwin
Instale Cygwin, versión x86. En el instalador, seleccione paquetes adicionales: gcc-core, g ++, binutils, automake, autoconf, cmake, libtool, gdb, make. Puede instalar las últimas versiones estables de paquetes.

También necesita registrar variables de entorno:
RUTA: ...; C: \ <path_to_Cygwin> \ Cygwin \ bin; C: \ <path_to_Cygwin> \ Cygwin \ lib
classpath: C: \ <path_to_Cygwin> \ Cygwin \ lib
STM32CubeIDE
El entorno se instala como de costumbre. Es recomendable instalar CubeIDE después de Cygwin, porque en este caso Cube recogerá la cadena de herramientas Cygwin existente.
Primero, cree un proyecto C ++ para la plataforma x86 Cygwin. Lo necesitaremos para, en primer lugar, verificar la funcionalidad de la cadena de herramientas y, en segundo lugar, lo utilizaremos como "donante" de la configuración del ensamblaje para el proyecto principal.
Elija Archivo> Nuevo> Proyecto C / C ++. Seleccione C ++ Managed Build. Creamos un proyecto de tipo hello world para la cadena de herramientas Cygwin GCC:

A continuación, deberá elegir qué configuraciones de ensamblaje crear. Solo depurar es suficiente.
Ahora puede verificar que el proyecto se está ejecutando seleccionando Proyecto> Construir todo. También es recomendable verificar la depuración en Cygwin ejecutando Ejecutar> Depurar como> Aplicación C / C ++ local. La aplicación mostrará "Hello world" en la consola dentro de CubeIDE.
Para que el depurador muestre líneas ejecutables en archivos de código fuente, debe configurar la visualización de rutas. En la ventana Ventana> Preferencias, en la pestaña C / C ++> Depurar, seleccione Ruta de búsqueda de origen y agregue una nueva pantalla: Agregar> Asignación de ruta. En la ventana, debe nombrar algo como una nueva pantalla y agregar líneas para los discos que están en el sistema:
- \ cygdrive \ c - C: \
- \ cygdrive \ g - G: \


Para una hermosa ejecución de prueba, también necesitamos un complemento para Eclipse con soporte para pruebas unitarias para C ++. Se instala directamente desde STM32CubeIDE: menú Ayuda> Instalar nuevo software, luego seleccione el Eclipse Repository e instale el complemento C / C ++ Unit Testing Support.

Crea la biblioteca de pruebas de Google
El código fuente de la biblioteca se puede tomar en: https://github.com/google/googletest/tree/release-1.8.1
Descomprima las fuentes, vaya al directorio googletest-release-1.8.1 con el terminal Cygwin y ejecute:
cmake . make
Después de un ensamblaje exitoso, el archivo de la biblioteca estática estará en ./googlemock/lib/libgtest.a, y los archivos de encabezado estarán en el directorio ./googletest/include/gtest/. Deberán copiarse en nuestro proyecto (o escribir la ruta a estos archivos en la configuración del proyecto).
Crear un proyecto para STM32
Diseño para placa de depuración STM32L476G-DISCO. El ejemplo no será demasiado sofisticado: hay dos LED en el tablero, permítales mostrar un contador binario de 00 a 11. Implementaremos un módulo separado para el contador, descrito en un par de archivos .h y .c, y escribiremos una prueba para ello.
El proyecto se puede crear como de costumbre, utilizando el configurador de cubos, lo principal es asegurarse de que los pines PB2 y PE8 estén configurados como salidas digitales. Al crear un proyecto, sería mejor especificar el tipo: C ++, esto será necesario para compilar las pruebas (el compilador de C seguirá compilando el código principal). La conversión de un proyecto desde C será posible más tarde, haciendo clic en el nombre del proyecto RMB y seleccionando "Convertir a C ++".
Para la compilación bajo MK y para las pruebas, necesitamos dos configuraciones de ensamblaje diferentes. En estas configuraciones, se recopilarán diferentes conjuntos de archivos: los principales obtendrán los módulos para trabajar con hardware y los módulos probados, y el de prueba obtendrá los mismos módulos probados y archivos de prueba. Por lo tanto, crearemos diferentes directorios en la raíz del proyecto: aplicación con el código de aplicación para MK (simplemente puede cambiar el nombre del directorio Src que creó Cube), Común para módulos que no dependen del hierro (que probaremos) y Pruebas para pruebas. Los directorios se pueden excluir del ensamblaje haciendo clic en RMB en su nombre, menú Configuración de recursos> Excluir de compilación.
Agregue nuestro módulo contador al directorio común:
Código led_counter(led_counter.h):
#ifndef LED_COUNTER_H_ #define LED_COUNTER_H_ #include <stdint.h> void Led_Counter_Init(); uint8_t Led_Counter_Get_Next(); #endif /* LED_COUNTER_H_ */
led_counter.cpp:
#include "led_counter.h" static uint8_t led_cnt_state = 0; void Led_Counter_Init() { led_cnt_state = 0; } uint8_t Led_Counter_Get_Next() { if(++led_cnt_state > 3) led_cnt_state = 0; return led_cnt_state; }
Los directorios Común y Pruebas deben agregarse a la ruta de búsqueda para incluir archivos: propiedades del proyecto (Propiedades)> C / C ++ General> Rutas y símbolos> Incluye.
Añadir a trabajar con LED principales
Fragmento main.cmain.c:
… #include "led_counter.h" … int main(void) { … Led_Counter_Init(); uint8_t led_state = 0; while (1) { led_state = Led_Counter_Get_Next(); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, led_state & (1<<0)); HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, led_state & (1<<1)); HAL_Delay(500); } … }
El proyecto debe compilarse y ejecutarse, y los LED deben parpadear.
Pruebas de escritura
Ahora eso para lo que todo comenzó.
Cree una nueva configuración de compilación a través de las propiedades del proyecto: Propiedades> Compilación C / C ++> Configuración> Administrar configuraciones. CubeIDE simplemente no le permitirá crear una configuración para compilar en Cygwin, así que cópiela del proyecto que creamos anteriormente:

Ahora debe cambiar a esta configuración y configurar las rutas a los archivos de origen y los archivos de encabezado. En las propiedades del proyecto en la pestaña Rutas y símbolos que prescribimos (al agregar una entrada, es mejor poner un toque en el campo "agregar a todos los idiomas"):
- Incluye - Pruebas / Inc, Común / Inc
- Bibliotecas - gtest
- Rutas de la biblioteca - Pruebas / Lib
- Ubicación de origen - / <prj_name> / Common y / <prj_name> / Tests (reemplace <prj_name> con el nombre del proyecto)
Luego, copie la biblioteca gtest - el archivo .a al directorio Tests / Lib en el proyecto, y los archivos de encabezado en la carpeta gtest - a la carpeta Tests / Inc. En la carpeta Pruebas, cree un nuevo archivo main.cpp en el que se ejecutarán las pruebas. Sus contenidos son estándar:
main.cpp:
#include "gtest/gtest.h" int main(int argc, char *argv[]) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
Además, para probar la configuración, crearemos una prueba que verificará que el tamaño del puntero sea de 32 bits en nuestro entorno (queremos asegurarnos de que sea el mismo que en el microcontrolador, para esto configuramos Cygwin de 32 bits).
Cree el siguiente archivo de prueba test_platform.cpp:
#include "gtest/gtest.h" TEST(PlatformTest, TestPointerSize) {
Ahora, si el proyecto se ejecuta como la aplicación C ++ habitual, la salida de depuración contendrá un mensaje de Google Test que indica que todas las pruebas han pasado.
La estructura del proyecto debería verse así:

Ahora escribiremos pruebas para nuestro módulo contador LED. Los archivos de prueba se pueden ubicar en la carpeta Pruebas:
test_led_counter.cpp #include "gtest/gtest.h" extern "C" { #include "led_counter.h" }
Para que los resultados de la prueba se muestren en una hermosa ventana, debe crear una nueva configuración de inicio en el menú Ejecutar> Configuraciones de depuración. El complemento instalado le permite crear configuraciones de tipo C / C ++ Unit. Créelo, llame a Ejecutar pruebas, seleccione la configuración de ensamblaje "Prueba" utilizada y desactive la casilla de verificación "detener al iniciar en" en la pestaña Depurador. Después de eso, se puede iniciar la configuración.
Para que aparezca una ventana con los resultados, selecciónela en Ventana> Mostrar vista> Otro> C / C ++> Unidad C / C ++.

Hecho Ahora el proyecto se puede compilar y ejecutar bajo el MK de destino como de costumbre. Cuando necesite ejecutar pruebas locales, cuando ejecute la configuración Ejecutar pruebas, el proyecto se reconstruirá automáticamente para x86, el entorno ejecutará las pruebas y mostrará el resultado.
Literatura
- J. Grenning. Desarrollo guiado por pruebas para C. incrustado: trabajo fundamental en pruebas unitarias de sistemas embebidos y en la aplicación de la metodología TDD.
- https://uncannier.com/unit-testing-of-embedded-firmware-part-1-software-confucius/ - Prueba unitaria del código del microcontrolador x86 en Texas Instruments Code Composer Studio, marco CppUTest
- http://blog.atollic.com/why-running-your-embedded-arm-cortex-code-on-a-host-pc-is-a-good-thing : un artículo sobre por qué podría ser útil ejecutar código para un microcontrolador en una plataforma de escritorio