Internet of Tests?

IoT für Webentwickler: Unit Testing mit PlatformIO
Keine Kommentare

Im vorigen Teil der IoT-Serie haben wir gesehen, wie man mit PlatformIO Embedded-Projekte in Cloud-basierte Continuous-Integration-Dienste integriert. Neben dem Kompilieren und Flashen ist auch das automatisierte Testen eine wesentliche Aufgabe für CI-Dienste. In diesem letzten Teil der Serie erkunden wir Unit Testing mit PlatformIO.

Unit Testing gehört für Softwareentwickler zur täglichen Routine, häufig auch in der Variante des Test-driven Developments: Zuerst die Tests schreiben, Implementierung ergänzen, bis alle Tests grün sind, um danach ggf. ein Refactoring anzugehen. Im Embedded-Bereich ergeben sich hier zusätzliche Herausforderungen. Zum einen läuft der Code auf einer anderen Hardwareplattform als auf dem Entwicklungssystem, zum anderen spricht er zwangsläufig Teile dieser Hardware an, die auf dem Entwicklungssystem gar nicht zur Verfügung stehen. Weiterhin hat man im Laufe des Produktlebenszyklus nicht nur mit verschiedenen Versionen der Firmware zu tun, sondern auch mit verschiedenen Versionen der Hardware. Erhöhte Komplexität und Variantenreichtum führen damit auch zu dem Wunsch, Tests möglichst automatisiert in verschiedenen Konstellationen durchführen zu können. Der Fokus dieses Teils der Artikelserie liegt darauf, die praktische Umsetzbarkeit von PlatformIOs test-Kommando für eigene Embedded-Projekte zu zeigen. Das PlatformIOFeature „Local Unit Testing“ kann in der Community Edition als Testversion nach einer Registrierung dreißig Tage lang kostenlos ausprobiert werden.

Was soll man testen und wo?

Firmware für Devices besteht aus mehreren Teilen, die untereinander verschiedene Abhängigkeiten besitzen können: C-Libraries, Board-spezifischer Code und ggf. ein Real-Time Operating System (RTOS) werden in der Regel vom Hersteller in angepasster Form mitgeliefert, anwendungsspezifischer Code vom Entwickler kann Abhängigkeiten von diesen Teile haben oder allgemeingültig sein (z.B. eine Zustandsmaschine). Abbildung 1 gibt eine Übersicht.

Abb. 1: Firmwarebestandteile

Abb. 1: Firmwarebestandteile

C-Libraries und Bestandteile sind bereits vom Hersteller getestet. Hardwareunabhängiger Code, z.B. Regelwerke oder Devicelogik, kann Unit-getestet werden, und zwar auch unabhängig vom Device, z.B. auf einem CIServer. Hardwarespezifischer wird auf dem Zieldevice oder auf einem Emulator getestet. Schließlich kann das ganze Device über seine Außenschnittstellen (z.B. APIs) Ende-zu-End getestet werden.

Für die Zusammenstellung von Code und Unit-Tests in einem Entwicklungsprojekt ist es erforderlich, Anwendungscode von Testcode trennen und je nach Testumfeld bauen und ablaufen lassen zu können:

  1. Hardwareunabhängigen Applikations-/Devicecode zusammen mit den Unit-Tests für eine native Plattform (x86, für das Entwicklernotebook oder den CIServer) bauen und ausführen
  2. Hardwarespezifischen Applikations-/Devicecode zusammen mit den Unit-Tests für die Zielplattform bauen und auf der Zielplattform ausführen
  3. Hardwarespezfischen Code ohne Unit-Tests bauen und auf der Zielplattform ausführen

In C/C++ sorgt das Buildsystem für die Zusammenstellung der Bestandteile. PlatformIO nutzt mit SCons ein Python-basiertes Build-Werkzeug, das konfigurierbar alle Bestandteile eines Entwicklungsprojekts finden und bauen kann. Als Grundlage verwenden wir ein Beispiel aus dem GitHub-Repository. Wer die vergangenen Teile dieser Artikelserie gemeistert hat, wird sich den Code problemlos herunterladen und mit PlatformIO bearbeiten können.

Unit-Test in PlatformIO schreiben

Beim ersten Blick auf die Verzeichnisstruktur fällt ein neuer Eintrag ins Auge: Im test-Verzeichnis wird nach PlatformIO-Konvention der Code rund um Unit Testing gesammelt. Um die verschiedenen Plattformen wie x86 und die Zielhardware auseinanderhalten zu können, verwaltet PlattformIO verschiedene Umgebungen innerhalb eines Projekts. In der Datei platformio.ini liegt für jede Umgebung ein Environment-Eintrag vor. Für unsere Standardhardware ESP8266 sieht der (durch PlatformIO automatisch generierte) Eintrag folgendermaßen aus:

[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino

Hier wird für die Umgebung „nodemcuv2“ die Kombination aus Embedded Platform, Board und Framework bestimmt. Es ist zulässig, beliebig viele Umgebungen aufzubauen, um somit für verschiedene Boards oder auch für die native Plattform bauen zu können. Listing 1 zeigt einen Umgebungseintrag für die native Plattform. Weiterhin wird hier ein test_filter definiert, in unserem Fall mit identischem Namen, um das Beispiel einfach zu halten.

[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
test_filter = nodemcuv2
[env:native]
platform = native
test_filter = native

Unterhalb des test/-Verzeichnisses liegen nun zwei Unterverzeichnisse, deren Namen den „Testfiltern“ entsprechen. Das bedeutet, dass beim Bauen bzw. Testen für eine bestimmte Umgebung nur dieser Code herangezogen wird. Die Umgebung kann den PIO-Befehlen run und test mit dem Parameter -e/–environment mitgegeben werden.

Unit-Tests in Platform IO ausführen

Im Beispiel der „native“-Umgebung liegt die Datei test/native/test.cpp vor. Um diesen Code zu bauen und auszuführen, kennt PIO das test-Kommando:

$ pio test -e native

Damit wird das Build-System angewiesen, für die native-Plattform zu bauen und über den test_filter die Dateien unter test/native mit einzubinden. Listing 2 zeigt die Ausgabe.

==== [test/native] Building... (1/2) ====
Please wait...

==== [test/native] Testing... (2/2) ====
test/native/test.cpp:93:test_display_clean_after_start  [PASSED]
test/native/test.cpp:94:test_display_shows_static_after_levelselector_init
  [PASSED]
test/native/test.cpp:95:test_display_level_line_shows_allowed_digits_only
  [PASSED]

-----------------------
3 Tests 0 Failures 0 Ignored
OK
(...)

Es wurden drei Unit-Tests definiert und erfolgreich ausgeführt. Für die Umsetzung des Testcodes ist man im Prinzip unabhängig von PlatformIO, kann aber Unity als leichtgewichtiges Unit-Testing-Framework für C- und Embedded-Projekte nutzen, da es automatisch mitgeliefert wird. Der Einsatz ist einfach und lässt sich in test/native/test.cpp nachzeichnen: Neben dem Inkludieren von <unity.h> werden eine Reihe von Testfunktionen definiert, die Testzustände über TEST_ASSERT_*-Makros umsetzen. Unity definiert dazu eine ganze Reihe von Assert-Makros. Zum Schluss werden die Tests in der main()-Funktion mit dem RUN_TEST-Makro eingebunden, und in ein UNITY_BEGIN()/UNITY_END() eingebaut. Damit erzeugt der Build-Vorgang ein ausführbares Binary, das die Tests aufruft.

Wie lässt sich der Rest des Embedded-Codes davor schützen, dass Testcode mitkompiliert wird? Die Antwort geben die ersten beiden Zeilen der Datei:

#ifndef ARDUINO
#ifdef UNIT_TEST

Damit ist garantiert, dass der Code nicht kompiliert wird, wenn die Arduino-Plattform vorliegt (d.h. für das Embedded-Board kompiliert wird) und wenn das Makro UNIT_TEST gesetzt ist (das automatisch durch PIO gesteuert wird).

API Summit 2018

From Bad to Good – OpenID Connect/OAuth

mit Daniel Wagner (VERBUND) und Anton Kalcik (business.software.engineering)

Unit-Tests auf dem Device

Interessanter wird es, wenn sich Unit-Tests auf dem Zielgerät ausführen lassen. test/nodemcuv2/test.cpp gibt dazu ein Beispiel. Es enthält ähnliche Unit-Test-Funktionen, steuert dabei aber spezifische Hardware wie ein OLED-Display an. Parallel zur Testausführung und zur Prüfung der Asserts lassen sich auch auf der Hardware Messungen durchführen.

Über den Schalter -e/–environment lässt sich die Zielhardware auswählen, sodass PlatformIO nun für das Entwicklungsboard kompiliert, es flasht und startet. Die Ausgabe der Unit-Tests wandert auf den seriellen Port der USB-Schnittstelle und wird vom test-Kommando direkt mit ausgegeben (Listing 3).

==== [test/nodemcuv2] Building... (1/3) ====
Please wait...
==== [test/nodemcuv2] Uploading... (2/3) ====
Please wait...
==== [test/nodemcuv2] Testing... (3/3) ====
If you don't see any output for the first 10 secs, please reset board (press reset button)

test/nodemcuv2/test.cpp:59:test_display_clean_after_start  [PASSED]
test/nodemcuv2/test.cpp:60:test_display_shows_static_after_levelselector_init
  [PASSED]
test/nodemcuv2/test.cpp:61:test_display_level_line_shows_allowed_digits_only
  [PASSED]
-----------------------
3 Tests 0 Failures 0 Ignored 
(...)

Remote-Ausführung vom CI-Server

PlatformIO kann die grundlegenden Aktionen wie Kompilieren, Flashen und Serial Monitor auch über seine Remoteschnittstelle ausführen. Im letzten Teil der Artikelserie haben wir die Remote-Funktionalität genutzt, um einen Cloud-basierten CI-Server einzubinden. Hier schließt sich nun der Kreis, denn PIO Remote unterstützt auch Remote-Testing. Damit lassen sich neben den nativen Unit-Tests auch die Tests auf dem Device selbst von einem CI-Server fernsteuern und automatisch durchführen. Im Beispiel von CircleCI lässt sich in der Datei circle.yml das passende Kommando einfügen:

pio test -e native
pio remote test -e nodemcuv2

Erst wenn sowohl der lokale als auch der remote Unit-Test erfolgreich durchgelaufen sind, wird das Board mit der Zielfirmware geflasht.

Fazit

Mit diesem Teil endet die Artikelserie. PlatformIO ist ein sehr flexibles Werkzeug und es erlaubt Webentwicklern den gewohnten Entwicklungsrhythmus und bekannte Werkzeuge wie IDEs, Code-Repository, Unit-Tests und Continuous Delivery/Continuous Integration in die Welt der IoT-Entwicklung mitzunehmen. PlatformIO wird kontinuierlich durch die Gründer und die Community weiterentwickelt, es bleibt also spannend, welche Features die Entwicklergemeinde in Zukunft erwarten kann.

Entwickler Magazin

Entwickler Magazin abonnierenDieser Artikel ist im Entwickler Magazin erschienen.

Natürlich können Sie das Entwickler Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -