この記事は?
別記事、RL78の環境でテストハーネスUnityを使用した単体テスト実行方法を紹介しました。
とはいえ、いくらテストケースといえどもプロダクトコードにいろいろの変更を加えたくない・・・何とかそのままテストだけ実行できないものかと。
そこで、プロダクトコードそのままに実行できるよう、勉強も兼ね、GDBで簡易的に単体テストを実施するテストハーネスを作ることにしました。
今回はテストケースを食わせてOK/NGを吐く、基本的な部分を作ります。
こんな感じで、e2studioでのビルド時にテストが走り、OK/NGの結果が出ることを目指します。
※作りかけの段階で備忘録かねて記事を書いています。途中で完成できずとん挫する可能性もあることをご了承ください・・・・
環境
使用する環境
- OS: Windows 11
- IDE: e2studio 2023-04 (23.4.0)
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のインストール時についてきたものを使います)
なお、別記事に記載された内容が実施されており、ビルド時にgdb起動・シミュレータでのテスト実行 までができていることを前提とします。
実装
AlwaysFailの実装
前の記事では、テストハーネスのインストール確認として、以下のような必ず失敗するテストケースを実行していました。
TEST(Test0, AlwaysFail)
{
// 何もせず、テストを失敗させて、メッセージを出力する
TEST_FAIL_MESSAGE("This test always fails.");
}
これを参考にし、以下を順に実現させます。
- 同様のテストケースを作成し
- テストケースを読み込んで実行させ
- その結果を数えて出力する
テストケースを作る
極力、既存のテストハーネス向けにテストケースを書く時と同じようにします。
また、実施テストケースの個数を管理したり、setup
/teardown
の処理を実施したりといったことも必要ですが、この辺りはユーザから隠蔽するようにします。
gdbのコマンドは基本的にコマンドを順に実行します。また、define
を使用すればコマンドの塊を関数のようにまとめて呼び出すことができます。
これを利用して、テストの開始・終了は、そのような見た目をしたgdbのコマンドとして定義し、その中でそれらの処理を実行するようにします。
ひとまず、LEDドライバ向けテストケースのファイル ledDriver.gdbcmd
を作成し、alwaysFailなテストを以下のように作成します。
# alwaysFail
defTest "alwaysFail"
set $assertResult = 1
testEnd
テストケースの成功/失敗は、gdbの変数に格納することで受け渡します。
今回はassertResult
として、0ならばOK、それ以外は失敗と定義します。
ここでは、テストケースが失敗するよう、1を代入します。
(本来は$assertResult
も隠蔽するべきですが、めんどくさいから簡単のためここはユーザから代入するようにしています)
また簡単のため、fail時のメッセージ出力もなしにします。
このように書くと、なんとなくテストケースを定義しているように見えます。
実際にテストケースとして機能させるためには、defTest
とtestEnd
を、それぞれコマンドとして定義し、さらにledDriver.gdbcmd
を、gdbのsource
コマンドで読み込んで実行するとよいでしょう。
テストケースを読み込んで実行させる
コマンドの定義をいったん後回しにし、テストケースを読み込んで実行する部分を作成します。
各テストケースを読み込んで実行するスクリプトファイルとして、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
を定義します。
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
を読み込む処理を入れます。
target sim
load
break main
r
source path\to\allTests.gdbcmd
q
ここまでしたら、あとはe2studioを立ち上げてビルドします。ただしUnityテストハーネスは使用しないので、関連部分は削除しても問題ありません。
ビルド後、以下のようにNG出力されれば成功です。
"alwaysFail"内で$assertResut
で0にセットしてやると、以下のようにテスト実施OKの出力が得られます。
# 0 alwaysFail
defTest "alwaysFail"
set $assertResult = 0
testEnd
〆
今回はテストケースを読み込まして実施する部分を作成しました。
やってみると意外とちゃんと結果が出力できていて、可能性を感じました。
まだ肝心の関数読み出し・結果確認などはできませんが、だんだん機能を充実させていきたいと思っています。