2022年3月27日日曜日

PlatformIOを利用してユニットテストを装置上で実施


背景

PlatformIOとはArduinoなどのプログラム作成時に便利な機能を提供してくれる開発環境です。
その機能の1つとしてテストの仕組みがあります。
気になっていたものの使い方を把握していなかったので、試しに使ってみました。
備忘録を兼ねて試したことを共有します。

使ったもの

  • VSCode + PlatformIO
  • 開発ボード
    ArduinoやESP32などです。
    今回はESP32の開発ボードを利用しました。

atmelavrやespressif32などplatformを利用したテストは、テストコードを開発対象に書き込んでテストするため、この記事で紹介するテストは開発対象の実機が要ります
platformをnativeとしてPC上で実行する仕組みがPlatformIOにはあるのですが、Arduino.hをincludeするとエラーになり、それに該当するモックが必要なのですがまだ良いものを見つけられていないので、今回は実機を利用してテストを行います。

platform=nativeで使い勝手の良いArduinoのモックをご存知でしたら、共有していただけると嬉しいです。

書き方の方針

PlatformIOのtestに関する説明を参考にしてプログラムを書きます。

Unit Testing
Unit Testing of a “Blink” Project

PlatformIOのプロジェクトのsrcディレクトリと同じ階層にtestディレクトリを作り、test/test_main.cppを作成します。
そのファイルがテスト実行コマンドで装置に書き込まれてテストされます。
project-to-test
|- platformio.ini
|- src
| |- main.cpp
|- test
|- test_main.cpp // 作成するテストプログラム

今回はESP32の開発ボードを使うので、「board = esp32dev」でプログラムを実行します。
platformio.ini
[platformio]
default_envs = esp32dev

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino


PlatformIOのtestではunityという仕組みが使われているので、それをincludeしてテストを記述します。
TEST_ASSERTの大まかな一覧はPlatformIOの解説ページに表示されています。
test/test_main.cpp
#include <Arduino.h>
#include <unity.h>

void test_plus() {
TEST_ASSERT_EQUAL_INT(2, 1+1);
TEST_ASSERT_EQUAL_INT(5, 2+3);
TEST_ASSERT_EQUAL_INT(10, 2+8);
}

void setup() {
delay(2000); // for board that does not support software reset
UNITY_BEGIN();
}

void loop() {
static int testCount = 0;
RUN_TEST(test_plus);
if (++testCount >= 3) {
UNITY_END();
}
}

「内部状態によって結果が変わることがあるからテストは3回連続で実行せよ」という勧めをtwitterで見かけたので、上記のコードで同じテストを3回を実施しています。

テストを実行: 実機が必要

装置をPCに接続し、下記のコマンドを実行するとtest_mainのビルドと書き込みが行われ、テストが実行されます。
pio test

platformio.iniに複数の環境を定義している場合は、pio runコマンドと同様に-eオプションやdefault_envsで実施対象の環境を絞れます。

テストが成功すれば下記のように全て「PASSED」になります。


失敗時の様子を見たいので、失敗するように2箇所書き換えて実行してみます。
  // TEST_ASSERT_EQUAL_INT(5, 2+3);
// TEST_ASSERT_EQUAL_INT(10, 2+8);
TEST_ASSERT_EQUAL_INT(5, 2+3+100);
TEST_ASSERT_EQUAL_INT(10, 2+8+100);

下記の結果になりました。


前のテストが失敗してもRUN_TESTは期待する回数実行されました。
RUN_TEST内でTEST_ASSERTが失敗すると、それ以後tのTEST_ASSERTは実施されませんでした。

一部のテストが失敗しても結果を確認したいテストがある場合は、RUN_TESTで呼ぶ関数を分けるのが良さそうです。

書き方1: ライブラリをテスト

テスト対象はlibディレクトリへの配置が推奨されているので、Arduinoのライブラリの形式でlibディレクトリにコードを配置します。
今回はstring_asukiaaaのテストを書いています。
project-to-test
|- platformio.ini
|- src
| |- main.cpp
|- test
| |- test_main.cpp // 作成するテストプログラム
|- lib
|- string_asukiaaa // テストしたいライブラリ
|- src
|- string_asukiaaa.h
|- string_asukiaaa.cpp

libディレクトリに定義されたライブラリは、src/main.cppと同様にライブラリ名でincludeできます。
test/test_main.cpp
#include <Arduino.h>
#include <string_asukiaaa.h>
#include <unity.h>

void test_padStart() {
TEST_ASSERT_EQUAL_STRING(
" 123", string_asukiaaa::padStart(String(123), 10, ' ').c_str());
TEST_ASSERT_EQUAL_STRING(
"00123", string_asukiaaa::padStart(String(123), 5, '0').c_str());
TEST_ASSERT_EQUAL_STRING(
"123", string_asukiaaa::padStart(String(123), 2, '0').c_str());
}

void setup() {
delay(2000); // for board that does not support software reset
UNITY_BEGIN();
}

void loop() {
static int testCount = 0;
RUN_TEST(test_padStart);
if (++testCount >= 3) {
UNITY_END();
}
}

ライブラリをテストできました。


書き方2: srcディレクトリにあるmain.cppではないファイルをテスト

下記のような位置にいるsrc/util.hppをテストする場合、相対パスを使えば読み込めます。
project-to-test
|- platformio.ini
|- src
| |- main.cpp
| |- util.hpp // テスト対象
|- test
|- test_main.cpp // 作成するテストプログラム

例として30を加算と100を乗算(掛け算)する関数を定義します。
src/util.hpp
#pragma once

int plus10(int v) { return v + 30; }
int mul100(int v) { return v * 100; }

対象となるファイルを相対パス「../src/util.hpp」でincludeすれば、test_main.cppで使えます。
test/test_main.cpp
#include <Arduino.h>
#include <unity.h>

#include "../src/util.hpp"

void test_plus30() {
TEST_ASSERT_EQUAL_INT(30, plus30(0));
TEST_ASSERT_EQUAL_INT(90, plus30(60));
}

void test_mul100() {
TEST_ASSERT_EQUAL_INT(0, mul100(0));
TEST_ASSERT_EQUAL_INT(100, mul100(1));
TEST_ASSERT_EQUAL_INT(25600, mul100(256));
}

void setup() {
delay(2000); // for board that does not support software reset
UNITY_BEGIN();
}

void loop() {
static int testCount = 0;
RUN_TEST(test_plus30);
RUN_TEST(test_mul100);
if (++testCount >= 3) {
UNITY_END();
}
}

src/util.hppで定義している関数をテストできました。


書き方3: loopとsetupをテスト時に除外するマクロを配置した上でmain.cppを取り込んで実行

src/main.cppで定義されている関数のテスト方法を紹介します。
project-to-test
|- platformio.ini
|- src
| |- main.cpp // テスト対象
|- test
|- test_main.cpp // 作成するテストプログラム

unityは実行時に「UNIT_TEST」というマクロが定義されるので、それがある場合はloopとsetupを定義しないマクロを配置します。
src/main.cpp
#include <Arduino.h>

int minus50(int v) { return v - 50; }

#ifndef UNIT_TEST
void setup() { Serial.begin(9600); }

void loop() {
int sec = millis() / 1000;
Serial.println(sec);
Serial.println(minus50(sec));
delay(2000);
}
#endif

setupとloopがマクロでテスト時に無効化される状態なら、相対パスでincludeできます。
test/test_main.cpp
#include <Arduino.h>
#include <unity.h>

#include "../src/main.cpp"

void test_minus50() {
TEST_ASSERT_EQUAL_INT(50, minus50(100));
TEST_ASSERT_EQUAL_INT(-20, minus50(30));
}

void setup() {
delay(2000); // for board that does not support software reset
UNITY_BEGIN();
}

void loop() {
static int testCount = 0;
RUN_TEST(test_minus50);
if (++testCount >= 3) {
UNITY_END();
}
}

src/main.cppに定義している関数をテストできました。


終わり

PlatformIOでの実機でのテストの動かし方を把握できました。
デスクトップ上でモックを使って実機無しでArduinoのテストを行える仕組みをご存知でしたら、コメントなどで共有していただけると嬉しいです。

参考

Unit Testing
Unit Testing of a “Blink” Project
VisualStudioCode+PlatformIOでユニットテストしてみました

0 件のコメント :