0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C言語のテストをGoっぽく書いてみる

Last updated at Posted at 2023-12-14

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 に属性を足せば色々拡張できそう。

イマイチなところ

  • FATALFreturn することでテストを中断させているのでヘルパー関数の中で呼んでもテストが続行される。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?