組込みで回帰テストの自動化!?

回帰テストとは、ざっくり言えばデグレが発生しないことを確認するテストです。動いていたものが動かなくなるというのは、個人的にも、会社的にもダメージが大きいのは分かっているのですが、変更箇所に関係ないと思っていたテスト項目を省略して、後で痛い目に合うこともよくある話です。

そんな精神的負担を軽くするため、回帰テストの自動化という壮大なテーマについて見ていきましょう。

回帰テストの問題点

「工数がかかる」
 「同じ機能を毎回テストするのはツライ」
 「全く変更していないところもテスト必要?」

全てのテスト項目を毎回クリアできれば理想的ですが、変更箇所に影響しないテスト項目を省略するのは、工数を削減する意味で必要です。ただし、どこを省略するかを見誤ると痛い目に合います。枯れた機能だからといって省略すると、想像を超えたところでバグは発生します。

しかしながら、こういった枯れた機能は、コードの変更も少なく固定的なテストができそうです。これが自動化できれば、工数面、精神面で少しはマシになるはずです。

どうやって自動化するか

ではどうやって自動化するか、自動化のための仕組みを考えます。Jenkinsを使えば自動的にツールを実行してくれますので、そのあたりはとりあえず置いといて、組込みでは必須となるターゲットシステムの制御と、テスト結果の判定の自動化を考えます。

JTAG-ICEを使えばターゲットシステムの制御は可能です。JTAG-ICEにはスクリプト機能を搭載しているものもありますので、そういった機能を利用して自動化を行います。また、テスト結果の判定は単純にログの比較を行います。テキストファイルでログを出力し、正解のログと比較してOK/NGを判定します。

ですので、例えば画面にメッセージが表示されているとか、ロボットアームが120度回転したとか、そういったアウトプットの最終形を判定することはしません。あくまでもプログラムのロジックが正しく動いているかどうかに絞って判定します。

必要なもの

  • ターゲットシステム
  • PALMiCE4(コンピューテックス製JTAG-ICE)
  • Jenkins
  • ビルド環境
  • パソコン

スクリプト機能を備えたJTAG-ICEとして、コンピューテックス製のPALMiCE4を使用します。Jenkinsは下記のURLからWindows版をダウンロードしてインストールします。

https://www.jenkins.io/

ターゲットシステムの制御をPALMiCE4のスクリプトで自動化し、回帰テスト全体の自動化はJenkinsで行います。

システムの構成

全体の接続構成は次の図のようになります。

システム構成図

パソコンにはJenkins、PALMiCE4のデバッガソフト「CSIDE」など必要となるソフトウェアをインストールしておきます。回帰テストを自動化するには、この構成で電源をオンにしておいて、Jenkinsにスケジュールを設定しておきます。

テストのシナリオ

次にテストのシナリオを考えてみます。ここでは本チャンを意識した、仮想的なテストのシナリオを考えます。プログラムとしては、

初期化 ⇒ 機能A ⇒ 終了処理

というシンプルな処理を想定します。機能Aが終了した時点でログを残して、機能Aが正しく実行されたかどうかを判定します。この処理のテスト手順は以下のようになります。

  1. プログラムのダウンロード
  2. 機能Aの終了ポイントにブレーク設定
  3. 初期化ルーチンと機能Aの実行
  4. 機能Aのアウトプットをログ保存
  5. 終了処理の実行
  6. 保存したログと正解のログと比較して合否判定

機能Aの終了ポイントでプログラムを一旦止めて、ログを出力するようにします。ログの内容は単純に機能Aの戻り値とします。このログの内容が正解のログと一致すればテストはOK、一致しなければNGとなります。

PALMiCE4でスクリプト作成

テストのシナリオが決まれば、それをスクリプトにします。PALMiCE4のデバッガ「CSIDE」にはCマクロというC言語準拠のスクリプトが用意されていますので、それを使って記述します。

Cマクロの定義ファイルはプロジェクトファイルと呼ばれる、起動時の設定を行うファイルに登録できます。CSIDE起動時のコマンドラインにプロジェクトファイルを指定することで、Cマクロが自動的に実行されます。また、プロジェクトファイルにはダウンロードファイルやブレークポイントの設定なども登録できますので、Cマクロでの記述を省略できます。

ではCSIDEを起動してダウンロードファイルとブレークポイントの設定を登録します。以下はCSIDE起動直後の画面です。

CSIDE起動画面

対象となるターゲットプログラムを選択してダウンロードします。

ダウンロード

ダウンロード後、今回のテスト対象となるプログラムを表示させます。

CSIDEメイン画面

今回はFunction_Aの後に結果を保存しますので、EndProcの前にブレークポイントを設定します。

ブレークポイント設定

次はCマクロを使って、実行、ブレーク待ち、ログ出力の部分を書いていきます。 Cマクロは普通のエディタでも書けますが、せっかくなのでCSIDEのマクロエディタを使用します。マクロエディタは[表示]メニューの[マクロ・エディタ]で開きます。

マクロエディタ

ここに関数名を「Test_A」としてCマクロを登録します。

マクロエディタ2

ご覧のように最後の行以外、普通のC言語です。全リストは以下の通りです。

FILE *fp;
char dir[256];

void Test_A()
{
    // ログの保存フォルダをこのソースと同じ場所にする
    getsrcdir(dir, 255);    // このソースのディレクトリ取得
    chdir(dir);             // カレントディレクトリ変更

    // InitProcとFunction_Aの実行
    Go();                  // ターゲット実行
    while (IsRunning());   // ブレークを待つ

    // result変数のログ出力
    fp = fopen("test_a.log","wt");
    fprintf(fp, "Function_A:Result=%d\n", @result);
    fclose(fp);

    // EndProcの実行
    Go();                   // ターゲット実行

    // CSIDEの終了
    Exit(false);
}

// マクロロード時にTest_Aを実行
$$Test_A();

この中でポイントとなるところを簡単に説明します。詳しいコマンドの説明はCSIDEのヘルプに記載されていますので、そちらをご覧ください。

内容 説明
Go() ターゲットプログラムを実行します。
while (IsRunning()) ターゲットプログラムがブレークするまで待ちます。IsRunningはターゲットプログラムが実行中の場合1を返します。
@result 変数名に@をつけるとターゲットプログラム側の変数参照となります。
Exit(false) CSIDEを確認無しで終了します。
$$Test_A() 関数定義の外に「$$関数名();」と記述すると、Cマクロの読み込み時にその関数が実行されます。Cマクロを自動実行させる場合は、このように記述します。

「while (IsRunning())」のループが終了したときは、ターゲットプログラムが停止しています。つまり、設定したブレークポイントで止まったということです。ここで、Function_Aの戻り値「result」の値をログに書き出します。ファイルへの出力はC言語の標準関数と同様にfprintfを使います。

このコードをマクロエディタの保存ボタンを押してファイルに保存します。ここでは「test_a.cmac」というファイル名で保存します。

マクロ保存

ついでに、マクロエディタには簡単なデバッグ機能がありますので、Cマクロが手順通りに動作することを確認しながら、作り込んでいくことができます。マクロエディタのツールバーに関数名を入力し、マクロの実行系ボタンを押せば、現在エディタに表示されている関数を実行することができます。以下の画面は「Test_A」が実行対象で、[トレース実行]ボタンを押した状態です。

マクロエディタ3

Cマクロの変数値を表示することもできますので、テストの処理を簡単にデバッグすることができます。

作ったCマクロを起動時に実行させるには[設定]メニューの[CSIDEの設定]で[ファイル]タブを選択し、下記の赤枠の部分に先ほど保存した「test_a.cmac」を入力します。

マクロ起動設定

最後に全ての設定内容をプロジェクトファイルに保存します。[ファイル]メニューの[名前をつけて保存][プロジェクト]でプロジェクトファイルに保存します。ここでは「test_a.cpf」という名前で保存します。

プロジェクト保存

これで、「test_a.cpf」を読み込んで起動すると、

ダウンロード ⇒ 実行 ⇒ ブレーク ⇒ ログ ⇒ 終了

までの処理が自動的に実行されます。

バッチファイルの作成

次にCSIDEの起動 ~ 終了と、結果の比較を行うためのバッチファイルを作成します。

処理としては数行なので、直接Jenkinsに登録してもいいのでは、と思いましたが、バッチファイルにしておく方が確認が簡単になります(個人的な意見です)。ここでは以下のようなバッチファイルを定義し、「test_a.bat」というファイル名で保存します。

setlocal
set TDIR=C:\jenkins\TEST
set CSIDE="C:\Program Files (x86)\CSIDE\Program\PALMiCE4 ARM.exe"

%CSIDE% "%TDIR%\test_a.cpf"
fc "%TDIR%\ok_test_a.log" "%TDIR%\test_a.log" > "%TDIR%\result_a.txt"
endlocal

「%CSIDE% "%TDIR%\test_a.cpf"」はプロジェクトファイルを使ってCSIDEを起動します。%CSIDE%はCSIDEの実行ファイルを定義した環境変数です。これにより、先に定義したCマクロが実行され、結果がtest_a.logに保存されます。その結果と正解のログファイルok_test_a.logを、ファイル比較コマンド(fc)で比較します。比較結果は「result_a.txt」に出力されます。

この段階でこのバッチを実行してみます。もちろん正解のログがありませんので、fcコマンドはエラーになりますが、ここで作成されたログを保存しておいて、正解のログとしてファイル名を変更しておきます。

test_a.log ⇒ ok_test_a.log

ここまででテストの半分のシナリオは終わりましたので、あとはこのバッチファイルを自動的に実行すればいいだけです。次回はこのバッチファイルをJenkinsで自動的に実行する作業を行います。