middle_unit
最小…よりも(ほんの)少し大きいテストフレームワーク
どもです。
今回は、単体テストのフレームワークについて書きます。
1.最小のフレームワーク
世の中に数多ある単体テストのフレームワークの中で、最も小さいモノは「min_unit」であると考えています。
(あくまで、私個人の意見/見解です。)
詳細は、コチラのサイトを参照してください。
このテストフレームワークは、たった1つのヘッダファイルのみから構成されています。
加えて、実装されているのも、「mu_assert()」と「mu_test_run()」の2つのマクロのみです。
これらの特徴から、このフレームワークはどのような環境(プラットフォーム)にも適用可能です。
私も、組み込み開発の単体テストでは、このフレームワークをよく使用します。
2.何が不満?
「min_unit」に対する不満は、下記が挙げられます。
- 値の比較を行うコードを、自分で書かなければならない
- 「=」と「==」を書き間違える可能性がある。
(実際、仕事でやらかしたことがあります。
このミス見つけたときは、シャレにならないくらい焦りました。)
- 「=」と「==」を書き間違える可能性がある。
- 指定できるメッセージが、テストの出力と期待値が一致しない場合にしか表示されない。
- テストの出力と期待値が一致した場合も、その旨知りたい!
- テストの進捗が知りたい!
- テストの出力と期待値が一致しないと、テストがその場で終了する。
- 1つ直して再実行して、また別のバグを修正して再実行して…というやり方は効率が悪い。
- 一通り最後までテストを実施して、NGとなるケースを洗い出したい!
- メッセージを表示するのに、自分でprintf文を書かなければならない。
- そこがイイトコロでもあるが、できれば書いてほしい!
- 実行したテスト、成功したテスト、失敗したテストのそれぞれの件数を教えてほしい。
- テストの件数の詳細(数)が知りたい!
3.不満を解消
3.1.min_unitの変更
というわけで、上記の不満を解消するべくmin_unitを変更したコードを、下記に示します。
#define mu_assert(message, expect, actual) \
do { \
if (!(expect == actual)) { \
printf(" = %s : NG\r\n", message); \
printf(" - expected : %d\r\n", (int)expect); \
printf(" - actual : %d\r\n", (int)actual); \
return message; \
} else { \
printf(" = %s : OK\r\n", message); \
} \
} while (0)
#define mu_run_test(test_name, test) \
do { \
printf(" [%s : START]\r\n", test_name); \
char *message = test(); \
tests_run++; \
if (message) { \
printf(" [%s : FAILED]\r\n", test_name); \
tests_failed++; \
} else { \
printf(" [%s : PASSED]\r\n", test_name); \
tests_passed++; \
} \
} while (0)
#define mu_run_all_test(test_name, test) \
do { \
printf("{%s : START}\r\n", test_name); \
tests_run = 0; \
tests_passed = 0; \
tests_failed = 0; \
test(); \
if (0 < tests_failed) { \
printf("{%s : FAILED}\r\n", test_name); \
} else { \
printf("{%s : ALL PASSED}\r\n", test_name); \
} \
printf(" [%d tests exeucted.]\n", tests_run); \
printf(" [%d tests passed.]\n", tests_passed); \
printf(" [%d tests failed.]\n", tests_failed); \
} while (0)
extern int tests_run;
extern int tests_passed;
extern int tests_failed;
3.2.min_unitからの変更点
min_unitから、以下のような変更を実施しています。
- mu_assertの引数を増やして、テストの出力と期待値を渡すように変更
- テストの出力と期待値が一致しない場合、それぞれの値を表示する。
- テストの出力と期待値が一致した場合でも、メッセージを表示する。
- mu_run_testの先頭で、テスト開始を示すメッセージを表示する。
- mu_run_testの完了時に、成功/失敗を示すメッセージを同時に表示する。
- mu_run_all_test()を新規に追加
一番大きな変更は、mu_assert()マクロのI/Fの変更かと思います。
マクロのI/Fが一部変更になっているので、minunitとは互換性がなくなってしまっています。
それでも、minunit側を少し変更するだけで十分に対応可能な範囲かと思います。
新規に追加した「mu_run_all_test()」マクロについては、実は使用しなくてもテストは実行可能です。
そのため、minunitとの互換性には影響はありません。
4.使ってみました
以下のような簡単な関数の単体テストを、改変/作成したフレームワークを使用して行ってみます。
int add(int val1, int val2, int* result)
{
int res = 0;
if (NULL == result) {
return 0;
} else {
*result = val1 + val2;
if (*result < 0) {
res = (-1);
} else if (0 <= *result) {
res = 1;
}
}
return res;
}
まず、テストケースを実行する処理の実装です。
実装は、下記のようになります。
int add(int val1, int val2, int* result);
static char* target_test_add_001()
{
int val1 = 0;
int val2 = 0;
int result = 0;
int res = 0;
res = add(val1, val2, &result);
mu_assert("result", result, 0);
mu_assert("res", res, 1);
return 0;
}
static char* target_test_add_002()
{
int val1 = -1;
int val2 = 0;
int result = 0;
int res = 0;
res = add(val1, val2, &result);
mu_assert("result", result, (-1));
mu_assert("res", res, (-1));
return 0;
}
static char* target_test_add_003()
{
int val1 = -1;
int val2 = 1;
int result = 0;
int res = 0;
res = add(val1, val2, (int*)&result);
mu_assert("result", result, 0);
mu_assert("res", res, 1);
return 0;
}
static char* target_test_add_004()
{
int val1 = -1;
int val2 = 0;
int result = 0;
int res = 0;
res = add(val1, val2, (int*)0);
mu_assert("res", res, 0);
return 0;
}
char* run_target_test_add()
{
mu_run_test("target_test_add_001", target_test_add_001);
mu_run_test("target_test_add_002", target_test_add_002);
mu_run_test("target_test_add_003", target_test_add_003);
mu_run_test("target_test_add_004", target_test_add_004);
return 0;
}
前述の通り、mu_assert()の引数を増やし、かつ第1引数には「結果がNGの場合に表示したいメッセージ」ではなく、「確認している内容を示す文字列」を設定しています。
また、main()関数の実装は、下記の通りです。
#include <stdio.h>
#include "../src/mid_unit.h"
int tests_run = 0;
int tests_passed = 0;
int tests_failed = 0;
char* run_target_test_add();
int main()
{
mu_run_all_test("run_target_test_add", run_target_test_add);
return 0;
}
先頭の3つの変数は、どうしてもここで宣言しなければならない、フレームワーク側で使用する変数の宣言です。
min_unitを使用した場合でも、同様の宣言が必要です。
これらのコードを実装したら、以下のコマンドを実行してビルドとテストの実行を行います。
gcc *.c -o example.exe ; ./example.exe
ビルドとテストの実行は、cygwin上で行っています。
テストを実行すると、コンソールには下図のような表示になります。
うん。
イイ感じです。
5.まとめ
今回、テストフレームワークである「min_unit」を、少しだけですが改変してみました。
結果として、これまで「かゆいところに手が届かない」と思っていたことが解消できました。
ほんの少しでも良いので、単体テストをやりやすくすることに貢献できていれば幸いです。
ではっ!
ex.公開しています
今回作成したフレームワークですが、エントリのタイトルにもあるように「middle_unit」と名付けました。
GitHubにて公開しています。
参考にしていただけたら、嬉しいです。
「いいね!」していただけたら、もっと嬉しいです。
ディスカッション
コメント一覧
まだ、コメントがありません