単体テストの効率化を考える(13)
同一ファイル内のサブ関数のスタブ置き換え(2)

2022年7月3日

どもです。

前回のエントリでは、テスト対象関数が実装されているソースコードを変更することなく、同じファイルに実装されたサブ関数をスタブ関数に置き換えてみる、という内容について書きました。

今回のエントリは、その続きとして「テスト対象関数の引数の数」と「サブ関数の数」に着目し、それぞれの数が増えた、または減った場合でも同様の方法が適用できるか否か確認してみます。

1. 結論

いきなり結論です。

はい。
全て適用可能です。

2. 細かく見てみる

引数、サブ関数の数の増減について、実際に変更しながら、実際にどのような結果になるのかを見ていきます。

2.1. 引数の数を増やした場合

まず、引数の数を増やした場合について見てみます。
引数の数を1つ増やし、「2個」にした場合です。
テスト対象関数は、以下のような実装になります。

void target_function1(int a, int b)
{
	printf("This is %s\n", __FUNCTION__);
	printf("a = %d\n", a);
	printf("b = %d\n", b);
	
	function1();
	function2();
}

なお、コードの上記以外の部分は前回のエントリのコードと同じなので、省略しています。

次に、テスト対象関数の呼び出しコードは、以下のようになります。

#include <stdio.h>

typedef void (*func_t)(void);
#define target_function1(a, b)	target_function1_stub_call(a, b, func_t function1, func_t function2)

#include "test.c"

--------(省略)--------

int main()
{
	int a = 10;
	int b = 11;
	target_function1_stub_call(a, b, function1_stub, function2_stub);
	
	return 0;
}

これをビルドして実行した結果は、下図の通りです。



テスト対象関数に引数の値がちゃんと渡されていることが確認できます。

2.2. 引数の数を減らした場合

次に、引数の数を減らした場合…要は「引数がない関数」に対して、この方法を適用した場合です。
なお、C言語では「引数がない関数」は、以下の2通りの宣言/実装が可能です。

void sample_func_no_arg();	//引数を記載しない
void sample_func_no_arg(void);	//「void」を記載して引数なしとする

また、これらのスタブ置き換え用のマクロの定義は、以下のパターンがあります。

#define target_function()	target_function1_stub_call(func_t function1, func_t function2)
#define target_function(a)	target_function2_stub_call(func_t function1, func_t function2)

従って、関数の宣言/実装と、それに対応するマクロの宣言の組み合わせは、以下の4通りとなります。

パターンの番号 関数の宣言 置き換え用のマクロの宣言
1 void target_function1() #define target_function1() …//省略
2 void target_function1() #define target_function1(a) …//省略
3 void target_function1(void) #define target_function1() …//省略
4 void target_function1(void) #define target_function1(a) …//省略

ところがこれらの中で、「No.3」の組み合わせのみ「ビルドエラー」となります。
具体的には、以下のようなエラーメッセージが表示されます。



原因は、関数の定義/実装と#defineで宣言された変換の内容が一致していないこと、のようです。
即ち、No.3の組み合わせの場合、宣言は「void型の引数がある」となっているのに、#defineされたコードでは「引数がない」とされています。
この齟齬により、コンパイラがコードを正しく解析できなくなりエラーとなっている…と考えられます。
(もし「それは違う!」ということであれば、ご指摘をお願いします。)

上記の「エラーとなるパターン」を除いたコードは、以下のようになります。

void target_function1()
{
	printf("This is %s\n", __FUNCTION__);
	
	function1();
	function2();
}

void target_function2(void)
{
	printf("This is %s\n", __FUNCTION__);
	
	function1();
	function2();
}

void target_function4()
{
	printf("This is %s\n", __FUNCTION__);
	
	function1();
	function2();
}
#define target_function1()	target_function1_stub_call(func_t function1, func_t function2)
#define target_function2(a)	target_function2_stub_call(func_t function1, func_t function2)
#define target_function4(a)	target_function4_stub_call(func_t function1, func_t function2)

(「3」は先述の通りビルドエラーになる組み合わせなので、省略しています。)

ここで、各関数およびマクロの末尾に付与した番号は、先述のテーブルのエントリNo.に対応しています。

これらのコードを実装してビルドして実行した結果は、下図の通りです。



テスト対象関数からスタブ関数が呼び出されていることが確認できます。

2.3. サブ関数を増やした場合

次に、サブ関数を増やした場合です。
前回のエントリでは、サブ関数が2つでした。
ここにサブ関数を1つ追加して、3つにしてみます。
また追加するサブ関数は、引数を1つ持つように定義します。
具体的なコードは、以下です。

#include <stdio.h>

void function1()
{
	printf("This is %s\n", __FUNCTION__);
}

void function2(void)
{
	printf("This is %s\n", __FUNCTION__);
}

void function3(int a)
{
	printf("This is %s\n", __FUNCTION__);
	printf("pow of a = %d\n", a * a);
}

void target_function1(int a)
{
	printf("This is %s\n", __FUNCTION__);
	
	function1();
	function2();
	function3(a);
}
#include <stdio.h>
typedef void (*func_t)(void);
typedef void (*func_t_2)(int a);
#define target_function1(a)	target_function1_stub_call(a, func_t function1, func_t function2, func_t_2 function3)

#include "test.c"

void function1_stub(void)
{
	printf("This is %s\n", __FUNCTION__);
}

void function2_stub(void)
{
	printf("This is %s\n", __FUNCTION__);
}

void function3_stub(int a)
{
	printf("This is %s\n", __FUNCTION__);
	printf("argument a = %d\n", a);
}

int main()
{
	int a = 12;
	target_function1_stub_call(a, function1_stub, function2_stub, function3_stub);
	
	return 0;
}

このコードをビルド、実行した結果は下図の通りです。



見ての通り、実装されたサブ関数ではなく、スタブ関数が呼び出されていることが分かります。

2.4. サブ関数を減らした場合

最後に、サブ関数を減らした場合です。
特に今回は、「サブ関数が無い」場合です。
サブ関数がないのであれば、そもそも置き換えマクロ自体必要はありません。
しかしながら、調査の一貫性の観点から、できる/できないの確認は実施します。

サブ関数がない場合のコードは、以下の通りです。

#include <stdio.h>

void target_function1(int a)
{
	printf("This is %s\n", __FUNCTION__);
}
#include <stdio.h>
#define target_function1(a)	target_function1_stub_call(a)

#include "test.c"

int main()
{
	int a = 12;
	target_function1_stub_call(a);
	
	return 0;
}

これらのコードをビルド、実行した結果は下図になります。



きちんと、defineマクロで定義された関数へと置き換えが行われていることが確認できました。

3. まとめ

今回のエントリでは、前回に引き続き「テスト対象関数が実装されているソースコードを変更することなく、同じファイルに実装されたサブ関数をスタブ関数に置き換えてみる」内容について、特にテスト対象関数の引数の戸数やサブ関数の個数が変化した場合に着目して書きました。
結論は、最初に書いた通り、「全てのパターンで対応可能」であることが分かりました。

2回のエントリにわたって紹介しましたが、この方法を適用することで、より効率敵に単体テストのコードが実装できるようになり、結果として単体テストを効率化、品質を担保できるようになると考えられます。

色々書きましたが、ここまでの内容が誰かの課題解消、問題解決の一助にでもなれば幸いです。

ではっ!