STM32CubeIDEにCppUTestを環境構築し、STM32マイコンでTDDする


# 変更履歴
 * ２０２３/7/7 追記
こちらに関連記事を書きました。よければ参照ください。

https://qiita.com/juraruming/items/2fbf398b9587757b17a2

シリアルコンソールから任意のタイミングで、任意のCppUTestのテストを実施できる環境をつくってみました。


# この記事はなに?
STM32マイコンの統合開発環境(以降STM32CubeIDE)にC/C++のテストフレームワーク(CppUTest)を環境構築しました。

ソースコードはつぎになります。
https://github.com/grace2riku/hello_stm32_cpputest

結果、STM32マイコンでもCppUTestでテストを実行できましたので環境構築の手順を書きます。
とはいえ既にWebに書いてある情報で何番煎じだ、という感じですがなにか参考になれば嬉しいです。

# 環境
## ホストPC環境
ホストPC環境はつぎのとおりです。
 * macOS Big Sur バージョン 11.6.4

## STM32CubeIDE
STM32CubeIDEのバージョンはつぎのとおりです。

* Version: 1.9.0 Build: 12015_20220302_0855 (UTC)
※2022/6/2現在の最新バージョンでした。

## CppUTest
C/C++の組込みソフトウェア向けテストフレームワークの選択肢はつぎがあるかと思います。

* CppUTest
* Unity
* Google Test

メモリ制約の厳しい小規模なマイコン上でTDDすることになると

* CppUnit
* Unity

が妥当かな・・・と思いました。
私が[【テスト駆動開発による組み込みプログラミング】](https://www.oreilly.co.jp/books/9784873116143/)の本を写経していることもあり、本で多く使われているCppUTestで環境構築したかった、というのが大きな理由です。


## ハードウェア
使用したマイコンボードはつぎです。このマイコンボードを使った理由ですが特に深い理由はありません。
過去、セミナー参加時に使ったことがあり他のSTM32マイコンボードより少し多く使っていて慣れている、くらいの理由です。

* NUCLEO-F446RE
* STM32F446RET6 64ピン
* Arm Cortex-M4コア 180MHz
* フラッシュ: 512Kbyte
* SRAM: 128Kbyte
* Arm Mbed対応 

![IMG_1054.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/1742d9de-e378-dcb2-b896-77fea62b0519.jpeg)


# 環境構築目標
環境構築するにあたり次を達成したいと思いました。

1. RAMでテストを実行したい
1. STM32CubeIDE単体でテスト結果がわかる

1の理由ですがTDDは
* テストを書く
* テスト失敗確認
* 成功するテストを書く
* テスト成功確認
* リファクタリング

のサイクルを素早く回すリズムが重要と思います。テスト実行に時間がかかるのは嫌だったので少しでも早く動くようRAMでテストを実行したいと思いました。また確認時間・書換え回数も気になるのでフラッシュを何度も書換えしたくない、というのも理由です。
これはSTM32CubeIDEでプロジェクト作成するとフラッシュ用・RAM用で2つのリンカスクリプトが用意されるのでRAM用のリンカスクリプトを選択することで実現できました。

2の理由はITMというものを使えばSTM32CubeIDEだけでテスト結果を確認できそう・・・と調査してわかりました。デバッグ情報はUART出力しテラタームなどのシリアルコンソールに表示する、というのがデバッグのセオリーだと思いますが、今回はSTM32CubeIDE単体でテスト結果がわかるように試してみました。


# 環境構築手順
STM32CubeIDEにCppUTestを構築する手順は既にいくつもWebに情報があります。
大きく分類すると3つの手順があります。

## CppUTestをStatic Libraryでビルドする
CppUTestをStatic Libraryでビルドし、STM32プロジェクトでリンクします。
[参考1](#参考)のリンクの手順とおりに作業していきました。

https://embeddedarea.com/unit-testing-in-stm32cubeide/

個人的につぎの点にはまりました。

* Static Libraryのプロジェクトを新規でつくる場合はC/C++ Projectではなく、STM３２ Projectにする。

![CppUtest Static Library.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/daf8ff6c-8509-c087-246b-fb2aa8a98f5d.png)


最初C/C++ ProjectでCppUTestのStatic Libraryをつくろうとしましたがつくれませんでした。


## STM32マイコンのプロジェクトを作成する
STM32マイコンのプロジェクトを作成します。さきほどつくったCppUTesｔのStatic Libraryをリンクします。
[参考1](#参考)のリンクの手順とおりに作業していきました。


## ITMを設定する
ITMを設定し、STM32CubeIDEのSWV ITM Data Consoleにテスト結果が表示されるようにします。
[参考3](#参考)のリンクの手順とおりに作業していきました。
今回のマイコンボードNUCLEO-F446REの場合はシリアルワイヤビューワ(SWW)のCore Clockはデフォルトの１６MHzにしました。


# ソースコードの説明
## テストコード
お試しでつぎのテストコードを書きました。

```c:test_src.cpp
#include "CppUTest/TestHarness.h"

TEST_GROUP(FirstTestGroup)
{

};

TEST(FirstTestGroup, FirstTest)
{
	FAIL("FAIL: FirstTestGroup, FirstTest\n");
}

TEST(FirstTestGroup, SecondTest)
{
	CHECK_EQUAL_ZERO(0);
}

TEST(FirstTestGroup, IntSize)
{
	LONGS_EQUAL(4, sizeof(int));
}
```

TEST(FirstTestGroup, FirstTest)のFirstTestGroupはテストグループの名称です。
２つ目の引数がテストの名称になります。

* FirstTestは失敗するテストで、引数の文字列を表示します。
* SecondTestは引数が０か否かを確認するテストで成功する見込みです。
* IntSizeはint型のバイト数を確認するテストで成功する見込みです(対象マイコンは３２bitマイコン)。

## テストコードの実行
テストを実行しているのは次のコードです。

```c:main.cpp main関数から該当部分のみ引用
  /* USER CODE BEGIN 2 */
	const char* empty[] = {};
	const char* command_v[] = {"cpputest", "-v"};

	printf("\n----- RunAllTests(0, empty) -----\n");
	CommandLineTestRunner::RunAllTests(0, empty);

	printf("\n----- RunAllTests(2, command_v) -----\n");
	CommandLineTestRunner::RunAllTests(2, command_v);

	/* USER CODE END 2 */
```

【CommandLineTestRunner::RunAllTests(0, empty)】はコマンドラインオプションなしでテストを実行しています。
【CommandLineTestRunner::RunAllTests(２, command_v)】はvオプションでテストを実行しています。テストの結果が詳しく表示されるオプションです。


# テスト動作確認
## テストコード実行
マイコンボードNUCLEO-F446REとPCをUSBケーブルで接続します。
STM３２CubeIDEのデバッグモードに移動し、プログラムを実行します。
つぎのようにSWV ITM Data Consoleにテスト結果が表示されます。
予想とおりの結果になりました。

* FirstTestはテスト失敗
* SecondTestはテスト成功
* IntSizeはテスト成功

vオプション付きでテストを実行すると少し詳しくテスト実行の結果が表示されています。


```
----- RunAllTests(0, empty) -----
..
../Core/Src/test_src.cpp:10: error: Failure in TEST(FirstTestGroup, FirstTest)
	FAIL: FirstTestGroup, FirstTest


.
Errors (1 failures, 3 tests, 3 ran, 3 checks, 0 ignored, 0 filtered out, 0 ms)


----- RunAllTests(2, command_v) -----
TEST(FirstTestGroup, IntSize) - 0 ms
TEST(FirstTestGroup, SecondTest) - 0 ms
TEST(FirstTestGroup, FirstTest)
../Core/Src/test_src.cpp:10: error: Failure in TEST(FirstTestGroup, FirstTest)
	FAIL: FirstTestGroup, FirstTest


 - 0 ms

Errors (1 failures, 3 tests, 3 ran, 3 checks, 0 ignored, 0 filtered out, 0 ms)
```

FirstTestが失敗し、SecondTest・IntSizeのテストは成功しています(テストは失敗したらメッセージ表示されます)。

次の動画はSTM32CubeIDEでデバッグモードに移動しテストを実行・結果を表示する動画です。

<iframe width="560" height="315" src="https://www.youtube.com/embed/8rb3picKkjw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# まとめ
[環境構築目標](#環境構築目標)に書いた目標を達成することができました。
気になった点をつぎに書きます。

## メモリ使用量
CppUTestのStatic Libraryをリンクし、テストを書くするとどのくらいのメモリを使用するのか気になりました。
資源が少ない組み込み向けマイコンは重要なポイントですよね。

![メモリ使用量.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/879449a4-fee3-0c00-ccc0-80205ebcf9f2.png)

RAMの使用量は **79.10%** になっていました。
このままだとこのマイコンボード(SRAM:128K)でTDDを続けるのは厳しそうですね・・・。
[参考２](#参考)に書いてあるように次の対応をするのが良さそうです。
　
* **Memory Leak Detection** 機能をリンクせずにメモリ容量を削減する
* サイズ優先の最適化をおこなう


# 最後に
最後まで読んでいただきありがとうございました。
なにか参考になれば嬉しいです。


# 参考
つぎのページを参考にさせていただきました。ありがとうございます。

1 STM32CubeIDEでCppUTest, STM32プロジェクトの作成する手順　 
https://embeddedarea.com/unit-testing-in-stm32cubeide/

2 Atollic TrueSTUDIOでCppUTest, STM32プロジェクトの作成する手順
https://qiita.com/tk23ohtani/items/1f1cc4b9fa58a04f520c

3 ITMの設定手順
https://moons.link/post-544/