【C言語】static(private)関数をユニットテストする3つの方法【単体テストのバッドノウハウ】
前書き:C言語のstatic関数は単体テストできます
C言語で単体テストを作成する際に、「どうやってstatic関数をテストコードから呼び出せばいいのか?」と迷った事はありませんか?例えば、以下のコードのprivate_func()を他のCソースファイル(例:テストコード)から呼び出せるでしょうか。
1 2 3 4 5 6 7 8 9 10 |
#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関数のラッパー関数(public)を作成する方法
- テスト対象のコードをincludeする方法
単体テストの時だけstatic指定子を消す方法
この方法は、#ifdef/#ifndef、#defineマクロ、gccコンパイルオプションを駆使して、static指定子を単体テスト時のみ消します。本記事で紹介する中で、この方法が最も自然です。
まず、任意のヘッダファイル(private.h)に#defineマクロと#ifdef/#ifndefを使用して、テスト時のみstatic指定子が消えるようにします。以下の例では、__TEST__が定義済みの場合はstatic指定子が消え、static関数がpublic関数として宣言されます。__TEST__が未定義の場合はstatic指定子が残り、pricate_func()関数の宣言はヘッダファイルから消えます。
1 2 3 4 5 6 7 8 9 |
#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()の宣言先がヘッダーファイルかソースファイルかが切り替わります。
1 2 3 4 5 6 7 8 9 10 |
#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)を以下のように実装します。
1 2 3 4 5 6 |
#include <stdio.h> #include "private.h" int main(void) { private_func(); } |
__TEST__の定義は、gccコンパイルオプション(”-Dオプション”)で指定します。以下に実例を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ 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)を作成します。
1 2 3 4 5 6 7 8 9 10 11 |
#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)からラッパー関数を呼び出すだけです。
1 |
void wrapper_private_func(void); |
1 2 3 4 5 6 |
#include <stdio.h> #include "private.h" int main(void) { wrapper_private_func(); } |
以下に実行例を示します。
1 2 3 4 5 |
$ gcc -o test test.c private.c $ ./test wrapper private function private function |
テスト対象のコードをincludeする方法
この方法は、# includeを用いて、static関数を定義したソースファイルをテストコードに読み込む方法です。私は、この方法を初めて聞いた時、「頭がいい方法だな」と思わず、「気持ち悪っ!」と嫌悪感を示しました。今でも良く覚えています。
実装方法は簡単で、テストコードを書いたソースファイル(test.c)にテスト対象ファイル(private.c)をincludeするだけです。C言語の#includeは、指定したファイルを#include指定行に展開する仕組みなので、ヘッダーファイル以外(=ソースファイル)も指定できます。
1 2 3 4 5 |
#include <stdio.h> static void private_func(void) { printf("private function\n"); } |
1 2 3 4 5 6 |
#include <stdio.h> #include "private.c" int main(void) { private_func(); } |
最後に、実行例を示します。
1 2 3 4 |
$ gcc -o test test.c private.c $ ./test private function |
余談:#includeでソースファイルを読み込む実例
coreutilsパッケージで提供されるfalseコマンド(必ず1を返すコマンド)は、#includeでソースファイルを読み込んでいます。以下の記事の下部に、実装解説がありますので、興味がある方は読んでみてください。
/etc/passwdに記載された/usr/sbin/nologin, /bin/falseとは何か【ログイン禁止】
ロシア人と国際結婚した地方エンジニア。
小学〜大学院、就職の全てが新潟。
大学の専攻は福祉工学だったのに、エンジニアとして就職。新卒入社した会社ではOS開発や半導体露光装置ソフトを開発。現在はサーバーサイドエンジニアとして修行中。HR/HM(メタル)とロシア妻が好き。サイトに関するお問い合わせやTwitterフォローは、お気軽にどうぞ。
1件の返信
[…] ユニットテストしないコードは複雑になりがちで、バグりやすい。(static関数もテスト可能であり、組み込みでもユニットテストする) […]