1
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でZig風のテスト

Posted at

はじめに

Zig言語では, テストを以下のように書ける.

test "test name" {
  ...
}

Cでは標準のテスト構文がないからテストを書くのがそこそこ面倒である.
そこで, CでもZigくらい簡潔に書けるテスト構文をマクロで実装した.

機能

  • testブロックの最後に到達したらテスト成功
  • expect(cond)で条件をチェックでき, falseの場合テスト失敗
  • unreachableで到達してはいけない箇所を明示でき, そこに到達したらテスト失敗
目指す形.c
int add(int x, int y) { return x + y; }

test(add_function_test) {
  expect(add(4, 5) == 9);
  expect(add(9, 2) == 11);

  int x = 15, y = 30;
  expect(add(x, y) == x + y);
}

test(unreachable_example) {
  for (;;) {
    break;
    unreachable;
  }
}

ソースコード

説明は後

testing.h
#include <setjmp.h>

#ifdef TEST_MODE
extern int test_success_count;
extern int test_failed_count;
// test() マクロ
#define test(name)                                                             \
  void test##name(jmp_buf);                                                    \
  __attribute__((constructor)) void testrunner##name() {                       \
    jmp_buf jb;                                                                \
    if (setjmp(jb) == 0)                                                       \
      test##name(jb);                                                          \
    else {                                                                     \
      puts(#name ": [NG]");                                                    \
      test_failed_count++;                                                     \
      return;                                                                  \
    }                                                                          \
    puts(#name ": [OK]");                                                      \
    test_success_count++;                                                      \
  }                                                                            \
  void test##name(jmp_buf jb)

#else
#define test(name) void test_dummy##name(jmp_buf jb)
#endif

// expect() マクロ
#define expect(cond)                                                           \
  if (!(cond)) {                                                               \
    printf("Failed at %s:%d " #cond " ", __FILE__, __LINE__);                  \
    longjmp(jb, 1);                                                            \
  }

// unreachable マクロ
#define unreachable                                                            \
  do {                                                                         \
    printf("Reached line %s:%d", __FILE__, __LINE__);                          \
    longjmp(jb, 1);                                                            \
  } while (0)

testing.c
#include "testing.h"
#include <stdio.h>

#ifdef TEST_MODE
#undef main

// 処理はconstructorとdestructorにやらせるからmain関数は空
int main() {}

// 他のファイルにあるであろうもともとのmain関数を無効化
#define main main_

int test_success_count;
int test_failed_count;

// テスト結果の集計を出力
__attribute__((destructor)) void report_test_result() {
  printf("\nPassed: %d/%d\n", test_success_count, test_success_count + test_failed_count);
}
#endif

使い方

testing.hをinclude
テストを有効化するときは-DTEST_MODEをつけてtesting.cもいっしょにビルド

説明

setjmpを使う理由

当初はtest##name()でのテストの結果をtestrunner##name()に返すときboolの返り値で返そうと思ってたけどそれだと

test(name) {
  ...
  return true;
}

みたいにブロックの終わりに毎回returnを書く必要があり, それは冗長でかっこ悪いと思ったからlongjmpを使って結果を返すことにした. これにより,

  • testブロックの最後に到達 → 成功
  • 途中でlongjmpが呼ばれる → 失敗

のように, Zigに近い構文を実現した.
longjmpを使う場面はマクロで隠蔽してあるから構造化プログラミングの観点からも問題ない(多分).

テスト自動実行について

testrunner##name()

constructor属性がついてるので定義だけしておけばプログラム開始時に勝手に呼ばれる.

report_test_result()

destructor属性がついてるので確実にすべてのテストが終わった後に勝手に呼ばれる.

エラー報告について

expect(), unreachableは失敗したとき

  • 現在のファイル名
  • 現在の行数
  • 条件式(expect()の場合)

を表示してからlongjmpする.

1
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
1
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?