Führen Sie Unit-Tests lokal in STM32CubeIDE unter Windows aus

Einführung


Jeder kennt die Vorteile von Unit-Tests. Wenn Sie Tests gleichzeitig mit dem Code schreiben, können Sie Fehler früher erkennen und anschließend keine Zeit mit zeitaufwändigem komplexem Debugging verschwenden. Bei der Embedded-Entwicklung weisen Unit-Tests Funktionen auf, die zum einen darauf zurückzuführen sind, dass der Code irgendwo tief im Darm des Geräts ausgeführt wird und es ziemlich schwierig ist, mit ihm zu interagieren, und zum anderen ist der Code stark an die Zielhardware gebunden .


Wenn das Projekt Fragmente enthält, die nicht von der Hardware abhängig sind und gleichzeitig eine recht komplexe Logik implementieren, bietet die Verwendung von Komponententests für sie den größten Vorteil. Beispielsweise kann es sich um die Implementierung eines Datenübertragungsprotokolls, verschiedener Berechnungen oder einer steuernden Zustandsmaschine handeln.


Es gibt drei Möglichkeiten, Unit-Tests für eingebettete Plattformen durchzuführen:


  1. Starten Sie direkt auf der Zielplattform. In diesem Fall können Sie mit der Ausrüstung des Geräts arbeiten, und der Code funktioniert genauso wie unter Kampfbedingungen. Zum Testen benötigen Sie jedoch physischen Zugriff auf das Gerät. Darüber hinaus ist der Testzyklus ziemlich lang, da ständig Code auf das Gerät heruntergeladen werden muss.
  2. Laufen auf einem Emulator. Diese Methode ist vor allem deshalb gut, weil Sie damit arbeiten können, auch wenn die Zielplattform nicht verfügbar ist (z. B. weil dies noch nicht geschehen ist). Nachteile sind die begrenzte Genauigkeit bei der Wiedergabe des Verhaltens von Eisen (und der umgebenden Welt) sowie die Schwierigkeit, einen solchen Emulator zu erzeugen.
  3. Wird auf dem Host-Computer (lokal) ausgeführt. Es funktioniert nicht mit dem Gerät (Sie können stattdessen Teststubs verwenden), aber die Tests werden schnell gestartet und funktionieren, und Sie benötigen keinen Zugriff auf das Zielgerät. Ein gutes Beispiel für die Verwendung dieser Methode ist das Testen der Implementierung eines Rechenalgorithmus auf einem Mikrocontroller, der an sich nicht von der Hardware abhängt, sondern die Sensordaten des Geräts verwendet. Das Testen eines Algorithmus mit einer realen Datenquelle ist sehr unpraktisch. Es ist viel besser, diese Messungen einmal aufzuzeichnen und Tests für die gespeicherten Daten durchzuführen. Dieses Skript führt Tests lokal aus und wird später erläutert.

Diese Veröffentlichung bietet eine Möglichkeit zum Konfigurieren von Komponententests in der STM32CubeIDE-Umgebung, basierend auf Eclipse und für die Entwicklung für Controller der STM32-Familie vorgesehen. Die Entwicklungssprache ist C, aber die Tests selbst sind in C ++ geschrieben. Die Tests werden auf einem Windows-Hostcomputer mit Cygwin ausgeführt. Als Testframework wird Google Test verwendet. Die Ergebnisse werden in einem speziellen Plug-In-Fenster für Unit-Tests angezeigt und können mit einer Schaltfläche aus dem Projekt für STM32 gestartet werden:



Die beschriebene Methode eignet sich für andere auf Eclipse basierende Entwicklungsumgebungen, es sei denn, die guten Hersteller haben sie aus Gründen der Benutzerfreundlichkeit zu stark gekürzt. Diese Methode funktioniert auch mit CubeIDE unter Linux, ohne dass Sie sich um Cygwin kümmern müssen.


Du wirst brauchen


  1. Cygwin 3.0.7 x86 (da die Tests für einen 32-Bit-Mikrocontroller gelten, verwenden wir eine 32-Bit-Umgebung auch auf einer 64-Bit-Plattform)
  2. STM32CubeIDE 1.0.2 für Windows.
  3. Google Test Framework 1.8.1

Installieren Sie Cygwin und STM32CubeIDE


Cygwin


Installieren Sie Cygwin, x86-Version. Wählen Sie im Installationsprogramm zusätzliche Pakete aus: gcc-core, g ++, binutils, automake, autoconf, cmake, libtool, gdb, make. Sie können die neuesten stabilen Versionen von Paketen installieren.



Sie müssen auch Umgebungsvariablen registrieren:


PFAD: ...; C: \ <Pfad_zu_Cygwin> \ Cygwin \ bin; C: \ <Pfad_zu_Cygwin> \ Cygwin \ lib
Klassenpfad: C: \ <Pfad_zu_Cygwin> \ Cygwin \ lib


STM32CubeIDE


Die Umgebung wird wie gewohnt installiert. Es ist ratsam, CubeIDE nach Cygwin zu installieren, da Cube in diesem Fall die vorhandene Cygwin-Toolchain übernimmt.


Erstellen Sie zunächst ein C ++ - Projekt für die x86-Cygwin-Plattform. Wir werden es benötigen, um erstens die Funktionalität der Toolchain zu überprüfen und zweitens, um es als „Spender“ der Baugruppenkonfiguration für das Hauptprojekt zu verwenden.


Wählen Sie "Datei"> "Neu"> "C / C ++ - Projekt". Wählen Sie C ++ Managed Build. Wir erstellen ein Projekt vom Typ Hallo Welt für die Cygwin GCC-Toolchain:



Als Nächstes müssen Sie auswählen, welche Baugruppenkonfigurationen erstellt werden sollen. Nur Debug ist genug.
Jetzt können Sie überprüfen, ob das Projekt ausgeführt wird, indem Sie Projekt> Alle erstellen wählen. Es ist auch ratsam, das Debuggen unter Cygwin zu überprüfen, indem Sie Ausführen> Debuggen als> Lokale C / C ++ - Anwendung ausführen. Die Anwendung gibt "Hallo Welt" an die Konsole in CubeIDE aus.


Damit der Debugger ausführbare Zeilen in Quellcodedateien anzeigt, müssen Sie die Anzeige von Pfaden konfigurieren. Wählen Sie im Fenster Fenster> Einstellungen auf der Registerkarte C / C ++> Debug die Option Quellensuchpfad aus und fügen Sie eine neue Anzeige hinzu: Hinzufügen> Pfadzuordnung. Im Fenster müssen Sie so etwas wie eine neue Anzeige benennen und Zeilen für die Festplatten im System hinzufügen:


  • \ cygdrive \ c - C: \
  • \ cygdrive \ g - G: \



Für einen schönen Testlauf benötigen wir außerdem ein Plug-In für Eclipse mit Unterstützung für Unit-Tests für C ++. Es wird direkt von STM32CubeIDE installiert: Menü Hilfe> Neue Software installieren, wählen Sie das Eclipse-Repository aus und installieren Sie das C / C ++ - Plugin zur Unterstützung von Unit-Tests.



Erstellen Sie die Google Test Library


Der Quellcode der Bibliothek finden Sie unter: https://github.com/google/googletest/tree/release-1.8.1
Entpacken Sie die Quellen, wechseln Sie mit dem Cygwin-Terminal in das Verzeichnis googletest-release-1.8.1 und führen Sie Folgendes aus:


cmake . make 

Nach erfolgreicher Assemblierung befindet sich die statische Bibliotheksdatei im Verzeichnis ./googlemock/lib/libgtest.a und die Headerdateien im Verzeichnis ./googletest/include/gtest/. Sie müssen in unser Projekt kopiert werden (oder schreiben Sie den Pfad zu diesen Dateien in die Projekteinstellungen).


Erstellen eines Projekts für STM32


Design für STM32L476G-DISCO Debug Board. Das Beispiel wird nicht zu komplex sein - es gibt zwei LEDs auf der Platine, die einen Binärzähler von 00 bis 11 anzeigen. Wir werden ein separates Modul für den Zähler implementieren, das in einem Paar von .h- und .c-Dateien beschrieben ist, und einen Test dafür schreiben.
Das Projekt kann wie gewohnt mit dem Cube-Konfigurator erstellt werden. Hauptsache muss sichergestellt werden, dass die PB2- und PE8-Pins als digitale Ausgänge konfiguriert sind. Wenn Sie ein Projekt erstellen, ist es besser, den Typ C ++ anzugeben. Dies wird zum Kompilieren der Tests benötigt (der Hauptcode wird weiterhin vom C-Compiler kompiliert). Das Konvertieren eines Projekts aus C ist später möglich, indem Sie auf den Namen des RMB-Projekts klicken und "In C ++ konvertieren" auswählen.


Für die Kompilierung unter MK und für Tests benötigen wir zwei verschiedene Baugruppenkonfigurationen. In diesen Konfigurationen werden verschiedene Dateigruppen gesammelt - die Hauptdateien erhalten die Module für die Arbeit mit Hardware und die getesteten Module, und die Testmodule erhalten dieselben getesteten Module und Testdateien. Daher erstellen wir verschiedene Verzeichnisse im Stammverzeichnis des Projekts - Anwendung mit dem Anwendungscode für MK (Sie können das von Cube erstellte Src-Verzeichnis einfach umbenennen), Allgemein für Module, die nicht von Eisen abhängig sind (die wir testen werden) und Tests für Tests. Verzeichnisse können von der Assembly ausgeschlossen werden, indem Sie in ihrem Namen im Menü Ressourcenkonfiguration> Vom Build ausschließen auf RMB klicken.


Fügen Sie unser Zählermodul zum allgemeinen Verzeichnis hinzu:


Led_counter Code

(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; } 

Die Verzeichnisse Common und Tests müssen dem Suchpfad für Include-Dateien hinzugefügt werden: Projekteigenschaften (Eigenschaften)> C / C ++ Allgemein> Pfade und Symbole> Includes.


Hinzufügen, um mit Haupt-LEDs zu arbeiten


Fragment main.c

main.c:


 /* USER CODE BEGIN Includes */ #include "led_counter.h" /* USER CODE END Includes */ … int main(void) { … /* USER CODE BEGIN WHILE */ Led_Counter_Init(); uint8_t led_state = 0; while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ 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); } /* USER CODE END 3 */ … } 

Das Projekt sollte kompiliert und ausgeführt werden und die LEDs sollten blinken.


Tests schreiben


Nun das, wofür alles begonnen wurde.


Erstellen Sie eine neue Build-Konfiguration über die Projekteigenschaften - Eigenschaften> C / C ++ - Build> Einstellungen> Konfigurationen verwalten. Mit CubeIDE können Sie einfach keine Konfiguration für die Erstellung unter Cygwin erstellen. Kopieren Sie sie daher aus dem zuvor erstellten Projekt:



Jetzt müssen Sie zu dieser Konfiguration wechseln und die Pfade zu den Quelldateien und Header-Dateien konfigurieren. In den Eigenschaften des Projekts auf der Registerkarte Pfade und Symbole, die wir vorschreiben (beim Hinzufügen eines Eintrags ist es besser, das Feld "Zu allen Sprachen hinzufügen" zu aktivieren):


  • Beinhaltet - Tests / Inc, Common / Inc.
  • Bibliotheken - gtest
  • Bibliothekspfade - Tests / Lib
  • Quellspeicherort - / <prj_name> / Common und / <prj_name> / Tests (ersetzen Sie <prj_name> durch den Projektnamen)

Kopieren Sie anschließend die gtest-Bibliothek - die .a-Datei in das Verzeichnis Tests / Lib in das Projekt und die Header-Dateien im Ordner gtest - in den Ordner Tests / Inc. Erstellen Sie im Ordner Tests eine neue Datei main.cpp, in der Tests ausgeführt werden. Sein Inhalt ist Standard:


main.cpp:


 /* * Unit tests main file */ #include "gtest/gtest.h" int main(int argc, char *argv[]) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } 

Um das Setup zu überprüfen, erstellen wir einen Test, der überprüft, ob die Größe des Zeigers in unserer Umgebung 32 Bit beträgt (wir möchten sicherstellen, dass er mit dem Mikrocontroller identisch ist, dafür setzen wir 32-Bit-Cygwin).


Erstellen Sie die folgende Testdatei test_platform.cpp:


 #include "gtest/gtest.h" TEST(PlatformTest, TestPointerSize) { //Check pointer size is 32 bit ASSERT_EQ(sizeof(void*)*8, 32U); } 

Wenn das Projekt nun als normale C ++ - Anwendung ausgeführt wird, wird in der Debug-Ausgabe eine Meldung von Google Test angezeigt, die besagt, dass alle Tests bestanden wurden.


Die Projektstruktur sollte ungefähr so ​​aussehen:


Jetzt werden wir Tests für unser LED-Zählermodul schreiben. Die Testdateien befinden sich im Ordner Tests:


test_led_counter.cpp
 #include "gtest/gtest.h" extern "C" { #include "led_counter.h" } // Test fixture class LedCounterTest: public ::testing::Test { protected: void SetUp() { Led_Counter_Init(); } }; // Check initial value TEST_F(LedCounterTest, TestInitialValue) { Led_Counter_Init(); ASSERT_EQ(Led_Counter_Get_Next(), 1); } // Check how value is incremented TEST_F(LedCounterTest, TestIncrementValue) { Led_Counter_Init(); unsigned int val = Led_Counter_Get_Next(); for(int i=0;i<1;i++) { ASSERT_EQ(Led_Counter_Get_Next(), ++val); } } // Check how value return to 0 after 3 TEST_F(LedCounterTest, TestZeroCrossing) { Led_Counter_Init(); for(int i=0;i<3;i++) { Led_Counter_Get_Next(); } ASSERT_EQ(Led_Counter_Get_Next(), 0); } 

Damit die Testergebnisse in einem schönen Fenster angezeigt werden, müssen Sie im Menü Ausführen> Debug-Konfigurationen eine neue Startkonfiguration erstellen. Mit dem installierten Plugin können Sie Konfigurationen wie C / C ++ Unit erstellen. Erstellen Sie es, rufen Sie Tests ausführen auf, wählen Sie die verwendete Konfiguration der Assembly "Test" aus und deaktivieren Sie das Kontrollkästchen "Beim Start stoppen um" auf der Registerkarte "Debugger". Danach kann die Konfiguration gestartet werden.


Damit ein Fenster mit den Ergebnissen angezeigt wird, wählen Sie es unter Fenster> Ansicht anzeigen> Andere> C / C ++> C / C ++ - Einheit aus.



Fertig! Jetzt kann das Projekt wie gewohnt unter dem Ziel-MK kompiliert und ausgeführt werden. Wenn Sie lokale Tests ausführen müssen und die Konfiguration "Tests ausführen" ausgeführt wird, wird das Projekt automatisch für x86 neu erstellt. Die Umgebung führt die Tests aus und zeigt das Ergebnis an.


Literatur


  1. J. Grenning. Testgetriebene Entwicklung für Embedded C. - Grundlegende Arbeiten zum Unit-Test von Embedded-Systemen und zur Anwendung der TDD-Methodik.
  2. https://uncannier.com/unit-testing-of-embedded-firmware-part-1-software-confucius/ - Unit-Test von x86-Mikrocontroller-Code in Texas Instruments Code Composer Studio, CppUTest-Framework
  3. http://blog.atollic.com/why-running-your-embedded-arm-cortex-code-on-a-host-pc-is-a-good-thing - ein Artikel darüber, warum es nützlich sein könnte, Code auszuführen für einen Mikrocontroller auf einer Desktop-Plattform

Source: https://habr.com/ru/post/de469367/


All Articles