前書き:C言語のstatic関数は単体テストできます
C言語で単体テストを作成する際に、**「どうやってstatic関数をテストコードから呼び出せばいいのか?」**と迷った事はありませんか?例えば、以下のコードのprivate_func()を他のCソースファイル(例:テストコード)から呼び出せるでしょうか。
#include <stdio.h>
int main(void) {
printf("Main function\n");
return 0;
}
static void private_func(void) {
printf("private function\n");
}
結論を言えば、static関数を単体テストする方法は3通りあります。本記事では、3通りの方法をそれぞれ紹介します。
static関数を単体テストする方法
- 単体テストの時だけstatic指定子を消す方法
- static関数のラッパー関数(public)を作成する方法
- テスト対象のコードをincludeする方法
単体テストの時だけstatic指定子を消す方法
この方法は、#ifdef/#ifndef、#defineマクロ、gccコンパイルオプションを駆使して、static指定子を単体テスト時のみ消します。本記事で紹介する中で、この方法が最も自然です。
まず、任意のヘッダファイル(private.h)に#defineマクロと#ifdef/#ifndefを使用して、テスト時のみstatic指定子が消えるようにします。以下の例では、__TEST__が定義済みの場合はstatic指定子が消え、static関数がpublic関数として宣言されます。__TEST__が未定義の場合はstatic指定子が残り、pricate_func()関数の宣言はヘッダファイルから消えます。
#ifndef __TEST__
#define STATIC static
#else
#define STATIC /* staicを指定しない */
#endif
#ifdef __TEST__
STATIC void private_func(void);
#endif
次に、ソースコード(private.c)では__TEST__が未定義の場合のみ、private_func()関数の宣言をします。このように実装すれば、__TEST__の定義・未定義によって、private_func()の宣言先がヘッダーファイルかソースファイルかが切り替わります。
#include <stdio.h>
#include "private.h"
#ifndef __TEST__
STATIC void private_func(void);
#endif
STATIC void private_func(void) {
printf("private function\n");
}
最後に、テストコード(test.c)を以下のように実装します。
#include <stdio.h>
#include "private.h"
int main(void) {
private_func();
}
__TEST__の定義は、gccコンパイルオプション("-Dオプション")で指定します。以下に実例を示します。
$ gcc -D__TEST__ -o test test.c private.c
(注釈):static関数をテストコードから呼び出し
$ ./test
private function
(注釈):gccコンパイルオプション("-D")がないと、テストコードのビルドはエラー。
通常は、Makefileでテストコードをビルドするかしないかを切り分ける。
$ gcc -o test test.c private.c
test.c: In function ‘main’:
test.c:5:5: warning: implicit declaration of function ‘private_func’ [-Wimplicit-function-declaration]
private_func();
^~~~~~~~~~~~
/usr/bin/ld: /tmp/ccG3PF2q.o: in function `main':
test.c:(.text+0xa): undefined reference to `private_func'
collect2: error: ld returned 1 exit status
static関数のラッパー関数(public)を作成する方法
この方法は、テスト対象static関数のラッパー関数(public)を作成します。無駄なラッパー関数がソースファイルに含まれるので、あまり好ましくないです。
まず、ソースコード(private.c)では、static関数(private_func)のラッピングする関数(wrapper_private_func)を作成します。
#include <stdio.h>
#include "private.h"
static void private_func(void) {
printf("private function\n");
}
void wrapper_private_func(void) {
printf("wrapper private function\n");
private_func();
}
残りの作業は、このラッパー関数の宣言をヘッダーファイル(private.h)に記載し、テストコード(test.c)からラッパー関数を呼び出すだけです。
void wrapper_private_func(void);
#include <stdio.h>
#include "private.h"
int main(void) {
wrapper_private_func();
}
以下に実行例を示します。
$ gcc -o test test.c private.c
$ ./test
wrapper private function
private function
テスト対象のコードをincludeする方法
この方法は、# includeを用いて、static関数を定義したソースファイルをテストコードに読み込む方法です。私は、この方法を初めて聞いた時、「頭がいい方法だな」と思わず、「気持ち悪っ!」と嫌悪感を示しました。今でも良く覚えています。
実装方法は簡単で、テストコードを書いたソースファイル(test.c)にテスト対象ファイル(private.c)をincludeするだけです。C言語の#includeは、指定したファイルを#include指定行に展開する仕組みなので、ヘッダーファイル以外(=ソースファイル)も指定できます。
#include <stdio.h>
static void private_func(void) {
printf("private function\n");
}
#include <stdio.h>
#include "private.c"
int main(void) {
private_func();
}
最後に、実行例を示します。
$ gcc -o test test.c private.c
$ ./test
private function
余談:#includeでソースファイルを読み込む実例
coreutilsパッケージで提供されるfalseコマンド(必ず1を返すコマンド)は、#includeでソースファイルを読み込んでいます。以下の記事の下部に、実装解説がありますので、興味がある方は読んでみてください。
