LoginSignup
3
1

More than 1 year has passed since last update.

[ESP-IDF][C++] ESP32でテスト駆動開発するための下準備

Last updated at Posted at 2022-01-07

はじめに

この記事は、『テスト駆動開発による組み込みプログラミング 』に触発された筆者が、ESP32のアプリをテスト駆動開発するために試行錯誤した成果の記録です。

次の2点を通して ESP32・ホストの両方でテストを実行できるプロジェクトの作成方法を紹介します。

  • ESP-IDF と CppUTest を用いたプロジェクトの作成方法
  • ホスト環境等で、ESP-IDFに依存する実装をテストダブルに置き換えてビルドする方法

おしながき

  • CppUTest の導入
  • ESP-IDF の導入
  • ESP-IDF/テストダブルの切り替え

検証環境

記事の内容は、下記の環境をでビルド・テスト実行できることを確認したものです。

  • ESP32-Devkit-C
  • Ubuntu 20.02 on WSL2 (Windows 11)
    • CMake
    • CppUTest
    • ESP-IDF

(注意) WSL で ESP32 と接続するにはひと工夫必要です。-> 別記事参照

CppUTest の導入

CppUTest は C/C++ 向けのテストライブラリの一つです。

ホスト上で実行するだけであれば、CppUTest のプリビルドを利用できるのですが、
アーキテクチャが違うので ESP32 上では実行できません。

なので、cpputest のソースをダウンロードして、プロジェクトのビルド時に cpputest も含めてビルドするようにします。

まず、CppUTest は GitHub で公開されているので、それをプロジェクトに追加します。
以下では、Git でバージョン管理しているプロジェクトに submodule として取り込む例です。

CppUTestをプロジェクトに追加する
~/MyPrj$ git submodule add https://github.com/cpputest/cpputest.git cpputest

次に、ダウンロードしたソースをビルド対象に指定します。
以下の例では、 CMake でビルドするために、CMakeLists.txt を作成しました。

テストに必要な実装は以下3種です。

  • src/CppUTest内のソースファイル
  • include内のヘッダーファイル
  • src/Platforms/コンパイラに依存する部分のソースファイル
MyPrj/CMakeLists.txt(抜粋)
# ソースファイルを追加する
set(CPPUTEST_HOME ${PROJECT_SOURCE_DIR}/cpputest)
add_subdirectory(${CPPUTEST_HOME}/src/CppUTest)

# 実行ファイルにコンパイラの依存を指定する
set(elf_file ${CMAKE_PROJECT_NAME}.elf)
add_executable(${elf_file}
    tests/AllTests.cpp
    ${CPPUTEST_HOME}/src/Platforms/Gcc/UtestPlatform.cpp # g++ でコンパイルするためにGccを指定
    ${SRC_FILES}
    ${TEST_FILES})

# 実行ファイルにインクルードする
target_include_directories(${elf_file}
    PUBLIC ${CPPUTEST_HOME}/include
    PUBLIC ${HEADER_DIRS})

この時点で、ホスト環境でのテストができるようになっているはずです。

ホスト環境でのテスト
$ cmake ..
$ cmake --build .
$ ./MyPrj.elf

Linking YourProject_tests
Running YourProject_tests
........
OK (8 tests, 8 ran, 35 checks, 0 ignored, 0 filtered out, 0 ms)

ESP-IDF の導入

idf_as_lib の方法を用いて、ホスト環境で実行していたテストを ESP32上で実行できるようにします。
これを用いることで、既存のプロジェクトを簡単に ESP-IDF に取り込むことができます。
ESP-IDFとプロジェクトとの依存を切りやすいのでおすすめです。

詳細は、公式のAPI GuidesExample を参照してください。

必要な対応は2点です。

  1. CMakeLists.txt に ESP-IDF 対応の項目を記載する
  2. main 関数が ESP-IDFから読み込まれるようにする

1 は次節でまとめて説明するので、ここでは 2 について説明します。

API Guides によると、ESP32でアプリ起動されるときは、まず app_main 関数が実行されます。
そこで、main 関数を実行する app_main 関数を追加します。

(注意) main を C++ で書いている場合には、 extern "C" を忘れないでください! app_main関数を実行する ESP-IDF側の実装 がC言語で書かれているので、C++ を実行するために必要です。

tests/RunAllTests.cpp
#include <CppUTest/CommandLineTestRunner.h>

extern "C" { // <- ESP-IDF側の呼び出し元が C言語で書かれているために必要

namespace Switch {
int main(int ac, char** av) {
  return CommandLineTestRunner::RunAllTests(ac, av);
}

void app_main() { // ESP-IDF 用の関数
  char* argv[] = {""};
  main(1, argv);
}
}  // namespace Switch
}

ESP-IDF/テストダブルの切り替え

ホスト上でテストする場合には、ESP-IDFは利用できないので、テストダブル(モック・スタブ)を使ってテストします。

テスト駆動開発による組み込みプログラミング 」によると、プラットフォームに依存する実装とそのテストダブルの切り替えはリンカで行うのが良いそうです。

下記の CMakeLists.txt では、CMake 実行時の引数の値によって ESP-IDF を利用する部分の ヘッダとソースを切り替えています。
また、リンクするライブラリは、 ESP32 用のビルドでのみ ESP-IDF を追加するようにしています。

CMakeLists.txt

...(前略)...

# ターゲットによってインクルード元を変える
if("${TARGET}" STREQUAL "esp32")
    include($ENV{IDF_PATH}/tools/cmake/idf.cmake)
    idf_build_process(esp32
                    SDKCONFIG ${CMAKE_BINARY_DIR}/sdkconfig
                    BUILD_DIR ${CMAKE_BINARY_DIR})

    set(HAL_DIR_NAME esp32)
    set(HEADER_DIRS_HAL ${HEADER_DIRS_HAL}
        $ENV{IDF_PATH}/components/esp_common/include)
else()
    set(HAL_DIR_NAME mocks)
endif()

...()...

# ターゲットによってリンク元を変える
if("${TARGET}" STREQUAL "esp32")
    target_link_libraries(${elf_file}
        idf::esp32
        idf::freertos 
        idf::spi_flash
        CppUTest
        Hal)

    idf_build_executable(${elf_file})
else()
    target_link_libraries(${elf_file} PUBLIC CppUTest Hal)
endif()

上記の CMakeLists を用いてビルドするとき、CMake の引数を変えることによって IDF/ホストを切り替えます。
* -DCMAKE_TOOLCHAIN_FILE: コンパイラの指定。ESP-IDFように指定が必要 (デフォルトは g++)。
* -DTARGET: ターゲットデバイスの指定。esp32 が指定されたときのみ、ESP-IDF を用いた実装にする。

ESP-IDFを使ってビルドする
$ cd build
$ cmake .. -DCMAKE_TOOLCHAIN_FILE=$IDF_PATH/tools/cmake/toolchain-esp32.cmake -DTARGET=esp32 -GNinja && cmake --build .
$ python $IDF_PATH/components/esptool_py/esptool/esptool.py -p /dev/{ESP32のポート} write_flash @flash_project_args
$ python $IDF_PATH/tools/idf_monitor.py -p /dev/{ESP32のポート}  MyPrj.elf
テストダブルを使ってビルドする
$ cmake ..
$ cmake --build .
$ ./MyPrj.elf
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1