単体テストの効率化を考える(10)
テストドライバの自動生成ツール

2021年8月9日

どもです。

前回の投稿から、だいぶ時間が経ってしまいました。
「単体テストの効率化を考える」シリーズ(?)の続きです。
前回のエントリの最後に、

テスト設計(書)から今回実装したテストドライバを自動で生成する仕組みを実装する。

と書きました。
この宣言通り、この「仕組み」を実装/ツールを作成しました。
本エントリでは、この作成したツールについて書きます。

0.前提

今回作成したツールは、以下の環境で開発/動作確認を行っています。
これ以外の環境での動作確認はできていません。
使用する場合には、注意をしてください。

開発/実行環境

項目 内容
OS Windows10 Pro(1909)
CPU i7-8700
メモリ 16GB
IDE VisualStudioCommnuity 2019
version 16.9.3
.NET Framework 4.7
Excel Office 2013

1. 基本的な使い方

それでは、本ツールの基本的な使い方を示していきます。

1.1. テスト設計書の設定

ツールを起動した場合、「テスト情報」が選択された状態の画面が表示されます。
ココで、「入力(テスト定義ファイル):」にテスト定義書をD&D(ドラッグ&ドロップ)して、設計書を指定します。

すると、「入力(テスト定義ファイル):」欄にテスト定義書のファイルのパス(フルパス)が表示されます。
また「出力ファイル:」の欄には、テスト定義書のファイルが格納されたディレクトリのパスが設定されます。
(「出力ファイル:」欄が空ではない場合には、ディレクトリのポスは設定されません。)

1.2. スタブ情報の設定

次にスタブの設定を行います。
設定できる項目は、「スタブのバッファのサイズ」です。
デフォルトでは、「100」が設定されています。
スタブのバッファのサイズについては、過去のエントリ(ココとかココ)を参照してください。
なお上限、下限、および入力された値のチェックは行われていませんので、注意してください。

1.3. ヘッダの指定

次に、ドライバがincludeするヘッダファイルを指定します。
includeするヘッダファイルは、標準ヘッダ/ユーザヘッダを別々に入力します。
「ヘッダ情報(ドライバ)」の「標準ヘッダ:」欄に標準ヘッダを、「ユーザヘッダ:」欄にユーザヘッダを、それぞれ設定します。
この時、以下の点に注意が必要です。

  1. 拡張子(.h)も含めて指定する
  2. 必要に応じて、フォルダも含めて指定する
    ex. gtest/gtest.h

なおヘッダの設定は、ドライバ/スタブ別々に設定します。
画面の基本的な構成が同じですので、入力する画面を間違えないように注意が必要です。

入力は、(現状は)ここまでです。

1.4. 実行

それでは、実際に実行します。
実行は、メニューバーの「実行」をクリックすればOK!です。
実行が正常に完了した場合には、以下の画面になります。

その結果、以下のようにフォルダが生成されます。
この中に、テストドライバおよびスタブのコードが格納されます。

2. 実際に使ってみる

使い方が分かったので、次は実際に使って見ます。
今回は、以下に示す関数を例に、この単体テストのテストドライバを生成します。
なお、簡単のために、他の関数の呼び出しが無いよう対象の関数を設計しています。

#include <stdio.h>

int sample_function_001(int input1, int input2)
{
	int result_data = 0;
	
	if (input1 < input2) {
		result_data = input2 - input1;
	} else {
		result_data = input1 - input2;
	}
	
	return result_data;
}

この単体テストに対する
また、この関数に対する単体テストを、以下のように設計しました。

(この設計書の書き方については、過去のエントリを参照してください。)
この設計に従ってツールが生成したコードが、以下になります。
単体テストのコードなので、冗長です。
しかし、設計した通りのテストを行うコードが生成されていることが分かります。
また、指定したツールで指定したヘッダファイルをincludeするコードが生成されていることが分かります。

/*
 *	sample_function_001 test driver source code.
 */
#include <stdio.h>
#include <windows.h>
#include <tchar.h>
#include "gtest/gtest.h"
#include "sample_function_001_test.h"

//Test target function.
int sample_function_001(int input1, int input2);

//Setup test case.(Called before all test functions tun.)
void sample_function_001_test::SetUpTestCase() { }

//Initialize test stub buffers.
void sample_function_001_test::SetUp()
{
}

//Finalize test case.
void sample_function_001_test::TearDown() { }

//Finalize test cases.(Called after all test functions have run.)
void sample_function_001_test::TearDownTestCase() { }

//Test method
TEST_F(sample_function_001_test, sample_function_001_1)
{
	//Declare arguments.
	int input1;
	int input2;

	//Setup test values.
	input1 = 0;
	input2 = 1;
	//Call target function.
	int returnValue = sample_function_001(input1, input2);

	//Check output and expectes.
	ASSERT_EQ(returnValue, 1);
}

TEST_F(sample_function_001_test, sample_function_001_2)
{
	//Declare arguments.
	int input1;
	int input2;

	//Setup test values.
	input1 = 0;
	input2 = 2;
	//Call target function.
	int returnValue = sample_function_001(input1, input2);

	//Check output and expectes.
	ASSERT_EQ(returnValue, 2);
}

TEST_F(sample_function_001_test, sample_function_001_3)
{
	//Declare arguments.
	int input1;
	int input2;

	//Setup test values.
	input1 = 0;
	input2 = 3;
	//Call target function.
	int returnValue = sample_function_001(input1, input2);

	//Check output and expectes.
	ASSERT_EQ(returnValue, 3);
}

TEST_F(sample_function_001_test, sample_function_001_4)
{
	//Declare arguments.
	int input1;
	int input2;

	//Setup test values.
	input1 = 0;
	input2 = 0;
	//Call target function.
	int returnValue = sample_function_001(input1, input2);

	//Check output and expectes.
	ASSERT_EQ(returnValue, 0);
}

TEST_F(sample_function_001_test, sample_function_001_5)
{
	//Declare arguments.
	int input1;
	int input2;

	//Setup test values.
	input1 = 1;
	input2 = 0;
	//Call target function.
	int returnValue = sample_function_001(input1, input2);

	//Check output and expectes.
	ASSERT_EQ(returnValue, 1);
}

TEST_F(sample_function_001_test, sample_function_001_6)
{
	//Declare arguments.
	int input1;
	int input2;

	//Setup test values.
	input1 = 2;
	input2 = 0;
	//Call target function.
	int returnValue = sample_function_001(input1, input2);

	//Check output and expectes.
	ASSERT_EQ(returnValue, 2);
}

TEST_F(sample_function_001_test, sample_function_001_7)
{
	//Declare arguments.
	int input1;
	int input2;

	//Setup test values.
	input1 = 3;
	input2 = 0;
	//Call target function.
	int returnValue = sample_function_001(input1, input2);

	//Check output and expectes.
	ASSERT_EQ(returnValue, 3);
}

4. まとめ

今回のエントリでは、単体テストのテスト設計書から、対応するテストコードを生成するツールについて紹介しました。
基本的な使い方と簡単な例のみの紹介ですが、まず基本的な部分については書けたのではないかと考えています。
次回は、この生成したコードを実際にビルドしてみます。

ではっ!

ex. 公開しています。

今回作成したツールは、GitHubにて公開しています。
最も基本的な機能が実現できている、ということで「Ver.0.1.0」としてReleaseもしています。
使って見ていただけたら幸いです。

また、本エントリは以下のエントリの続きとなっています。
コチラも併せて読んでいただけたら、幸いです。
単体テストの効率化を考える(1)-はじめに
単体テストの効率化を考える(2)-スタブの戻り値
単体テストの効率化を考える(3)-スタブの引数
単体テストの効率化を考える(4)-スタブの引数(ダブルポインタ)
単体テストの効率化を考える(5)-スタブの自動生成への入力
単体テストの効率化を考える(6)-スタブの自動生成ツール
単体テストの効率化を考える(7)-単体テストの設計/設計資料
単体テストの効率化を考える(8)-単体テスト設計書
単体テストの効率化を考える(9)-テストドライバを実装してみる

2021.08.09 更新

ドライバコードがヘッダファイルをインクルードしていませんでした。
ツールの不具合が原因でした。
不具合と併せて、修正をしました。