gdbでRL78向け簡易テストハーネスを作る その1: alwaysFailを作る

# この記事は？
[別記事](https://qiita.com/Ke_N_551/items/a937fb5ed5bb604c2df2)、RL78の環境でテストハーネスUnityを使用した単体テスト実行方法を紹介しました。
とはいえ、いくらテストケースといえどもプロダクトコードにいろいろの変更を加えたくない・・・何とかそのままテストだけ実行できないものかと。
そこで、プロダクトコードそのままに実行できるよう、勉強も兼ね、GDBで簡易的に単体テストを実施するテストハーネスを作ることにしました。
今回はテストケースを食わせてOK/NGを吐く、基本的な部分を作ります。

こんな感じで、e2studioでのビルド時にテストが走り、OK/NGの結果が出ることを目指します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166634/39bdb663-f814-c3cb-971d-ad3be4f61cf7.png)

※作りかけの段階で備忘録かねて記事を書いています。途中で完成できずとん挫する可能性もあることをご了承ください・・・・

# 環境
## 使用する環境
* OS: Windows 11
* IDE: e2studio 2023-04 (23.4.0)
[https://www.renesas.com/jp/ja/software-tool/e-studio](https://www.renesas.com/jp/ja/software-tool/e-studio)
* コンパイラ：CC-RL v1.12.00
* GDB：GNU gdb (GDB) 7.8.2-20211130-Build_1  
（e2studioのインストール時についてきたものを使います）

なお、[別記事](https://qiita.com/Ke_N_551/items/a937fb5ed5bb604c2df2)に記載された内容が実施されており、ビルド時にgdb起動・シミュレータでのテスト実行 までができていることを前提とします。

# 実装
## AlwaysFailの実装
前の記事では、テストハーネスのインストール確認として、以下のような必ず失敗するテストケースを実行していました。
```C
TEST(Test0, AlwaysFail)
{
    // 何もせず、テストを失敗させて、メッセージを出力する
    TEST_FAIL_MESSAGE("This test always fails.");
}
```
これを参考にし、以下を順に実現させます。

* 同様のテストケースを作成し
* テストケースを読み込んで実行させ
* その結果を数えて出力する


### テストケースを作る
極力、既存のテストハーネス向けにテストケースを書く時と同じようにします。
また、実施テストケースの個数を管理したり、`setup`/`teardown`の処理を実施したりといったことも必要ですが、この辺りはユーザから隠蔽するようにします。

gdbのコマンドは基本的にコマンドを順に実行します。また、[`define`](https://sourceware.org/gdb/onlinedocs/gdb/Define.html)を使用すればコマンドの塊を関数のようにまとめて呼び出すことができます。
これを利用して、テストの開始・終了は、**そのような見た目をしたgdbのコマンドとして定義**し、その中でそれらの処理を実行するようにします。

ひとまず、LEDドライバ向けテストケースのファイル `ledDriver.gdbcmd`を作成し、alwaysFailなテストを以下のように作成します。

```ledDriver.gdbcmd
# alwaysFail
defTest "alwaysFail"
    set $assertResult = 1
testEnd
```
テストケースの成功/失敗は、gdbの変数に格納することで受け渡します。
今回は`assertResult`として、0ならばOK、それ以外は失敗と定義します。
ここでは、テストケースが失敗するよう、1を代入します。
（本来は`$assertResult`も隠蔽するべきですが、~~めんどくさいから~~簡単のためここはユーザから代入するようにしています）
また簡単のため、fail時のメッセージ出力もなしにします。

このように書くと、なんとなくテストケースを定義しているように見えます。
実際にテストケースとして機能させるためには、`defTest`と`testEnd`を、それぞれコマンドとして定義し、さらに`ledDriver.gdbcmd`を、gdbの`source`コマンドで読み込んで実行するとよいでしょう。

### テストケースを読み込んで実行させる
コマンドの定義をいったん後回しにし、テストケースを読み込んで実行する部分を作成します。

各テストケースを読み込んで実行するスクリプトファイルとして、`allTests.gdbcmd`を以下のように作成します。

```allTests.gdbcmd
printf "test start\n"
printf "--------------------------\n"

source path\to\testMacros.gdbcmd

source path\to\ledDriver.gdbcmd

printf "--------------------------\n"
printf "test finished\n"
printf "%d tests, %d failed\n", $cases, $fails
if $cases == $oks
printf "reult: OK\n"
else
printf "result: FAIL\n"
printf "--------------------------\n"
```
`ledDriver.gdbcmd`は、前節で作成したテストケースを定義するファイルです。
この実施のためには、`defTest`と`testEnd`がコマンドとして定義されている必要がありました。このようにテストの実施に必要なコマンド・変数定義を、`testMacros.gdbcmd`ファイル内に実装し、テストケース実施前に`source`するようにします。

また、全テストケース実行完了後には、実施テスト件数・失敗件数を基にテスト全体のOK/NGを判断し、出力します。
前回の記事では、出力に”OK”という文字列があるかどうかでテスト結果を判断していました。これに基づき、成功時は”OK”、失敗時は”FAIL”と出力されるようにします。

### 実施テスト件数・結果を数えて出力する


`testMacros.gdbcmd`を作成し、その中に`defTest`と`testEnd`を定義します。

```testMacros.gdbcmd
set $cases = 0
set $fails = 0
set $oks = 0

define defTest
    unset environment $testName
    set $testName = $arg0
    set $cases = ($cases + 1)
end

define testEnd
    if $assertResult != 0
    set $fails = ($fails + 1)
    printf "testcase \"%s\" failed\n", $testName
    else
    set $oks = ($oks + 1)
end

```

テスト前後に実施する内容を以下のように決めました。
* テスト前：
    * テストケース名を受け取る（NGが起きたテストケースを特定するため
    * 実施済みテストケースをインクリメントする（実施件数を表示するため）
    * `setup`を実施する（今回は実装しません）
* テスト後：
    * テスト結果OK/NGを判断し、該当するテストケース数（OK数orNG数）をインクリメントする
    * `teardown`を実施する（今回は実装しません）
    * テスト失敗の場合、失敗したテストケース名を出力する

テスト結果のOK/NGは、先ほど用意した`$assertResult`変数を参照して行います。
このため、テストケースの実行中に`$assertResult`に何らかの結果が代入されるようにする必要があります。

テスト名は、`defTest`コマンドの引数として受け取ります。一番目の引数なので、`$arg0`変数に代入されます。
`defTest`から`testEnd`までは単一のテストケースのみが実行されることを前提とし、`$testName`変受け取ったテスト名を代入します。
テスト名は、テスト失敗時の出力に使用します。

これら処理を、それぞれ`defTest`および`testEnd`という名前のユーザコマンドとして定義します。
また、これらコマンドの中で使用する変数も、あらかじめ0に初期化しておきます。
こうすることで、必要な変数（テスト件数を格納）の初期化とコマンドの定義が、テストケース実施前に行われます。

最後に、e2studioのビルド完了後にgdb上で実施される`autorun.gdbcmd`に、`allTests`を読み込む処理を入れます。

```autorun.gdbcmd
target sim
load
break main
r
source path\to\allTests.gdbcmd
q
```
ここまでしたら、あとはe2studioを立ち上げてビルドします。ただしUnityテストハーネスは使用しないので、関連部分は削除しても問題ありません。
ビルド後、以下のようにNG出力されれば成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166634/1367939b-9243-1762-6660-7dba43d22aa6.png)

"alwaysFail"内で`$assertResut`で0にセットしてやると、以下のようにテスト実施OKの出力が得られます。

```ledDriver.gdbcmd
# 0 alwaysFail
defTest "alwaysFail"
    set $assertResult = 0
testEnd
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166634/cb86a793-d998-b3e4-51e3-11ae8b62a145.png)

# 〆
今回はテストケースを読み込まして実施する部分を作成しました。
やってみると意外とちゃんと結果が出力できていて、可能性を感じました。

まだ肝心の関数読み出し・結果確認などはできませんが、だんだん機能を充実させていきたいと思っています。