単体テストの効率化を考える(6)
スタブの自動生成ツール

2022年7月3日

どもです。
「単体テストの効率化について考える」の6回目です。
1回目から5回目まではコチラ
単体テストの効率化を考える(1)-はじめに
単体テストの効率化を考える(2)-スタブの戻り値
単体テストの効率化を考える(3)-スタブの引数
単体テストの効率化を考える(4)-スタブの引数(ダブルポインタ)
単体テストの効率化を考える(5)-スタブの自動生成への入力

1.振り返り

前回(5回目)までで、スタブを自動生成して単体テストを効率化するために、スタブで行うべき処理と、スタブを自動生成するツールへの入力データは明確にできたかと思います。
そこで今回は、いよいよスタブの自動生成ツールの本体について書きます。
今回のエントリの内容を書きたくて、これまでの「単体テストの効率化について~」を書いてきました。
本番(?)です!

2.スタブの自動生成ツール

エクセルに記載された関数情報を元に、その関数の「スタブ」を生成するツールです。
まずは、画面は下記!
think_about_unit_test_006_01
実にシンプルです。
必要な情報の入力欄(「スタブ定義ファイル」と「スタブ出力フォルダ」)については、一般的なアプリケーションのように、ファイルやダイアログから選択しません。
直接入力か、「ドラッグアンドドロップ」でのみ入力可能としています。
think_about_unit_test_006_02
(図は、「ドラッグアンドドロップ」のイメージ)
必要な情報を入力すると、「実行」ボタンが有効になります。
「実行」ボタンが有効になったら、ボタンを押します。
スタブの生成が完了すると、完了を通知するメッセージが表示されます。
すると、「スタブ出力フォルダ」に指定した場所に、「stub.c」と「stub.h」というファイルが生成されます。
このファイルが、入力情報に入力された関数のに対するスタブが実装されたファイルです。
think_about_unit_test_006_03

3.生成されたスタブ

今回作成したツールが生成するスタブの中身です。
スタブ生成には、下記のデータを入力としています。
think_about_unit_test_005_03

3.1.ソースファイル(.c)

3.1.1.ソース

ソースコードの一部を抜粋します。
(全体のスクリーンショットが撮れないので、コードを載せます。)

#include  <stdio.h>
#define     STUB_BUFFER_SIZE            (100)
/*------------------------------------*/
/*----                            ----*/
/*---- Start SubFunction_001 stub ----*/
/*----                            ----*/
/*------------------------------------*/
int SubFunction_001_called_count = 0;
int SubFunction_001_a[STUB_BUFFER_SIZE];
int SubFunction_001_b[STUB_BUFFER_SIZE];
int* SubFunction_001_c[STUB_BUFFER_SIZE];
int SubFunction_001_c_value[STUB_BUFFER_SIZE];
int SubFunction_001_ret_val[STUB_BUFFER_SIZE];
int SubFunction_001
(
    int a
    , int b
    , int* c
)
{
    int ret_val = SubFunction_001_ret_val[SubFunction_001_called_count];
    SubFunction_001_a[SubFunction_001_called_count] = a;
    SubFunction_001_b[SubFunction_001_called_count] = b;
    SubFunction_001_c[SubFunction_001_called_count] = c;
    *c = SubFunction_001_c_value[SubFunction_001_called_count];
    SubFunction_001_called_count++;
    return ret_val;
}
void SubFunction_001_init()
{
    int id1 = 0;
    int id2 = 0;
    for (id1 = 0; id1 < STUB_BUFFER_SIZE; id1++) {
        SubFunction_001_a[id1] = 0;
        SubFunction_001_b[id1] = 0;
        SubFunction_001_c[id1] = NULL;
        SubFunction_001_c_value[id1] = 0;
        SubFunction_001_ret_val[id1] = 0;
    }
    SubFunction_001_called_count = 0;
}

1つの関数に対するスタブは、

  1. 変数(バッファ)の宣言
  2. スタブの処理本体
  3. 変数(バッファ)の初期化関数

の3つの部分で構成されます。
次からは、各部分について(簡単ですが)解説します。

3.1.2.変数(バッファ)の宣言

まず、変数(バッファ)の宣言です。
各変数の内容と、ツールで規定されているフォーマット/命名規則です。

変数名 内容 フォーマット/命名規則
呼び出し回数 SubFunction_001_called_count {関数名}_called_count
SubFunction_001_a[] 引数aの値を格納するバッファ {関数名}_{引数名}[(バッファサイズのマクロ名)]
SubFunction_001_b[] 引数bの値を格納するバッファ {関数名}_{引数名}[(マクロ)]
SubFunction_001_c[] 引数c(格納されているアドレス)の値を格納するバッファ {関数名}_{引数名}[(マクロ)]
SubFunction_001_c_value[] 引数cの実体に設定する値のバッファ
引数がポインタ、かつ「出力」の場合にのみ出力される。
{関数名}_{引数名}[(マクロ)]
SubFunction_001_ret_val[] 関数/スタブが返す値を引数cの実体に格納する値のバッファ
関数が値を返す場合のみ、出力される。
{関数名}_ret_val[(マクロ)]

スタブの呼び出し回数を示す変数は、「{関数名}_called_count」という命名規則に則って生成されます。
引数のバッファは、「{関数名}_{引数名}[(バッファサイズのマクロ)]」という命名規則に則って生成されます。
引数がポインタかつ「出力」である場合に、ポインタの実体に設定する値のバッファは、「{関数名}_{引数名}_value[(バッファサイズのマクロ)]」という命名規則に則って生成されます。
また、スタブの戻り値は、「{関数名}_ret_val[(バッファサイズのマクロ)]」という命名規則に則って生成されます。
これは、スタブが戻り値を持つ場合のみ生成されます。
戻り値を持たない場合には、生成されません。
各バッファのデータ型は、入力としているエクセルの表に記載された値が、そのまま採用されます。

3.1.3.スタブの処理本体

次に、スタブの処理本体です。
スタブの本体では、以下の順番で処理を行います。

  1. 戻り値のラッチ(※戻り値を持つ場合のみ)
  2. 引数の値のコピー
    • 引数の数ぶん実行
    • 引数が「出力」の場合は出力値の設定も実施
  3. 呼び出し回数の更新(インクリメント)
  4. return(※戻り値を持つ場合のみ)

関数の定義によって行数は増減はしますが、やっていることは変化しません。

3.1.4.バッファの初期化

これまでのエントリでは、一切触れてこなかった項目です。
テストのデータを格納するバッファですが、初期化が必要です。
その初期化を、この関数で実施します。
初期化する値は、以下のようにしています。

変数 初期化値
呼び出し回数 0
引数の値 プリミティブ ⇒ 0
ポインタ ⇒ NULL

呼び出し回数以外のバッファは、基本的に配列です。
なので、for文で配列の全要素を初期化しています。

3.2.ヘッダファイル

コチラも、コードの一部の抜粋になります。

/*------------------------------------*/
/*----                            ----*/
/*---- Start SubFunction_001 stub ----*/
/*----                            ----*/
/*------------------------------------*/
extern int SubFunction_001_called_count;
extern int SubFunction_001_a[];
extern int SubFunction_001_b[];
extern int* SubFunction_001_c[];
extern int SubFunction_001_c_value[];
extern int SubFunction_001_ret_val[];
extern void SubFunction_001_init();

各バッファ、および初期化関数のextern宣言です。
スタブ本体については、宣言をしていません。
テストをする際には、スタブの元になる関数が別途宣言されている(はず!)だからです。

4.まとめ

今回、スタブの自動生成するツールについて書きました。
本来ツールを紹介するだけであれば、今回のエントリだけで十分でした。
しかし、今回のエントリだけでは、このツールが生成するコードがどのような意味を持つのかを伝えられないと思い、これまでの複数回にわたるエントリを書いてきました。
長いエントリになってしまいましたが、これでツールとツールが生成するコードの「ナゼ?」が少しでも伝わればよいかと思います。
そして最初のエントリにも書きましたが、このツールが「単体テストの効率化」に少しでも貢献できればよいな、と思います。

ではっ!

5.公開しています。

(2020年5月11日追記:ここから)
今回のエントリで紹介したツールは、GitHubにて公開しています。
ビルドは、以下の環境でのみ確認できています。
[table id=VSCommunity_DotNet /]
また、下記に示すパッケージをも適用しています。

パッケージ名 バージョン
ClosedXML v0.94.2
OpenCover v4.7.922

(2020年5月11日追記:ここまで)

ex.余談

同じようなツール、過去に作成して公開しています。
詳しいエントリは、コチラです。
このツールは、コマンドラインツールでWindows上/Cygwin環境でしか動作しません。
そのため、少~し使い勝手が悪くなっていました。
今回のツールでは、この使い勝手の悪さも解消ができたかな、と思います。