tl;dr
- GoのtestingパッケージのようなものをCで書いてみた。
- 実用に耐えるものかは考えてない遊びのプログラム。
動機
- Cのテストを書きたいが趣味のプログラムなので大層なフレームワークは必要なかった。
- この記事に感化されてbetter assert.h的な簡素なもので満足できそうだと思った。
実装
typedef struct {
char *name;
bool ok;
} TestingT;
typedef void (*TestFunc)(TestingT *t);
#define RUN_TEST(test_func) \
do { \
TestingT t = { .name = #test_func, .ok = true }; \
printf("=== RUN %s\n", t.name); \
test_func(&t); \
if (t.ok) { \
printf("--- PASS %s\n", t.name); \
} else { \
printf("--- FAIL %s\n", t.name); \
} \
} while(0)
#define ERRORF(t, fmt, ...) \
do { \
t->ok = false; \
printf("\t%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
} while(0)
#define FATALF(t, fmt, ...) \
do { \
t->ok = false; \
printf("\t%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
return; \
} while(0)
- テスト関数のシグニチャは
TestFunc
のようにする。 - RUN_TESTにテスト関数を渡して実行する。
- ほかはGoと同様。
使い方
#define LEN(array) (sizeof(array) / sizeof((array)[0]))
void fizzbuzz(int x, char *buf) {
if (x % 15 == 0)
sprintf(buf, "fizzbuzz");
else if (x % 3 == 0)
sprintf(buf, "fizz");
else if (x % 5 == 0)
sprintf(buf, "bazz");
else
sprintf(buf, "%d", x);
}
void test_fizzbuzz(TestingT *t) {
struct {
int input;
char *want;
} tests[] = {
{ 1, "1"},
{ 2, "2"},
{ 3, "fizz"},
{ 4, "4"},
{ 5, "buzz"},
{ 6, "fizz"},
{ 7, "7"},
{ 8, "8"},
{ 9, "fizz"},
{ 10, "buzz"},
{ 11, "11"},
{ 12, "fizz"},
{ 13, "13"},
{ 14, "14"},
{ 15, "fizzbuzz"},
};
for (int i = 0; i < LEN(tests); i++) {
char got[10];
fizzbuzz(tests[i].input, got);
if (strcmp(got, tests[i].want) != 0) {
ERRORF(t, "[input=%d] want %s, but got %s", tests[i].input, tests[i].want, got);
}
}
}
int main(int argc, char **argv)
{
RUN_TEST(test_fizzbuzz);
return 0;
}
/* 実行結果
$ ./unittest
=== RUN test_fizzbuzz
example.c:46: [input=5] want buzz, but got bazz
example.c:46: [input=10] want buzz, but got bazz
--- FAIL test_fizzbuzz
*/
-
"buzz"
のタイポを検出できている。 -
ERRORF
はテストを中断させないので5, 10の両方のケースで失敗している。assert系の関数やマクロだとこれができないことが多い。
イイところ
- Goのテストの思想そのままだが、テストのために新しい記法を覚える必要がない。
-
ERRORF
が即時失敗にならないのでFATALF
とちゃんと使い分ければTable-Driven Testを書ける。 -
TestingT
に属性を足せば色々拡張できそう。
イマイチなところ
-
FATALF
はreturn
することでテストを中断させているのでヘルパー関数の中で呼んでもテストが続行される。