/** * @file kc_ut.c * @brief 単体テストモジュール * @copyright 2003 - 2024 Nomura Kei */ #include <stdio.h> #include <kc_ut.h> #include <kc_term.h> #include <kc_assert.h> // 最大登録関数 #define KC_UT_ENTRY_MAX (16384) #define KC_UT_OUTPUT stderr /** * テスト用関数リスト。 */ typedef struct KcUtEntry_ { UtType type; const char *title; void (*func)(void); } KcUtEntry; /** * テスト管理情報 */ typedef struct { int test_counter; //!< 実施テスト数 int ok_counter; //!< OK 件数 int ng_counter; //!< NG 件数 int func_counter; //!< 登録関数の数 int test_case; //!< テストケース数 int run_index; //!< 実行関数インデックス bool is_ng; //!< NG が発生したか否かの判定用 KcUtEntry entry[KC_UT_ENTRY_MAX]; } KcUtInfo; // プロトタイプ宣言 static void KcUt_assert_handler(const char *msg); static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); static void KcUt_run(KcUt *ut); // 公開関数 /** * 単体テスト用のインスタンスを取得します。 * * @return 単体テスト用の唯一のインスタンス */ KcUt *KcUt_get_instance(void) { static KcUt instance; static KcUtInfo info = { .test_counter = 0, .ok_counter = 0, .ng_counter = 0, .func_counter = 0, .run_index = 0, .test_case = 0, .is_ng = false, }; if (instance._info != &info) { // 初回のみ初期化する。 instance.add = KcUt_add; instance.run = KcUt_run; instance._info = &info; } return &instance; } // 内部関数 /** * 単体テスト用 assert ハンドラ。 * * @param msg メッセージ */ static void KcUt_assert_handler(const char *msg) { KcUt *ut = KcUt_get_instance(); ((KcUtInfo *)ut->_info)->is_ng = true; fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); } /** * テストケースまたは、事前処理/事後処理の関数を追加します。 * * @param ut KcUT インスタンス * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) * @param title タイトル * @param func 追加する関数 */ static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) { KcUtInfo *info = (KcUtInfo *)ut->_info; info->entry[info->func_counter].type = type; info->entry[info->func_counter].title = title; info->entry[info->func_counter].func = func; info->func_counter++; if (type == UT_TESTCASE) { info->test_case++; } } /** * テストケースを実行します。 * * @param ut KcUT インスタンス */ static void KcUt_run(KcUt *ut) { // assert_handler を単体試験用ハンドラに切り替えておく。 assert_handler = KcUt_assert_handler; const char *result; KcUtInfo *info = (KcUtInfo *)ut->_info; // KcMemory_start(true); for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) { info->is_ng = false; info->entry[info->run_index].func(); if (info->entry[info->run_index].type == UT_TESTCASE) { // テストケースの場合のみエラー判定&結果表示する。 info->test_counter++; if (!info->is_ng) { // テスト成功 info->ok_counter++; result = KC_TERM_GRN "OK" KC_TERM_DEF; } else { // テスト失敗 info->ng_counter++; result = KC_TERM_RED "NG" KC_TERM_DEF; } // 実行結果表示 printf( KC_TERM_BLD KC_TERM_CYN "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter, info->entry[info->run_index].title, result); fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); } } printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); printf("\n"); }