c言語ユニットテストフレームワーク--CuTest
14870 ワード
1、紹介
CuTestは微小なC言語ユニットのテストボックスで、私が今まで見た最も簡潔なテストフレームワークの一つで、2つのファイルしかありません.CuTest.cとCuTest.h、すべてのコードを合わせて千行未満です.スズメは小さいが、五臓がそろっており、テストの構築、テストの管理、テスト文は、すべて含まれている.
2、CuTest剖析
2.1アサーション
1つのテストcaseがコード実地に落ちるかどうかは,テスト値と期待値を比較することであり,断言に用いる.#define CuAssertStrEquals(tc,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
#define CuAssertStrEquals_Msg(tc,ms,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
#define CuAssertIntEquals(tc,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
#define CuAssertIntEquals_Msg(tc,ms,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
#define CuAssertDblEquals(tc,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac),(dl))
#define CuAssertDblEquals_Msg(tc,ms,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac),(dl))
#define CuAssertPtrEquals(tc,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
#define CuAssertPtrEquals_Msg(tc,ms,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
......
......
数値テストを例に、次のように実装されたCuAssertIntEquals.void CuAssertIntEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
int expected, int actual)
{
char buf[STRING_MAX];
if (expected == actual) return;
sprintf(buf, "expected but was ", expected, actual);
CuFail_Line(tc, file, line, message, buf);
}
テストに成功すると、returnによってこの関数が返されます.ほとんどのテストフレームワークの哲学はlinux哲学に似ていて、小さくても美しくて、少なくてもよくて、異常がなければユーザーを邪魔しません.エラーが発生した場合は、ファイルパス/ファイル名/関数名、行番号などのエラーメッセージが保存されます.sprintf(buf, "expected but was ", expected, actual);
CuFail_Line(tc, file, line, message, buf);
さらに深く進むと、上記の関数はstringにエラーメッセージを接続し、CuFailInternal関数に渡します.CuFailInternal関数名から、この関数こそ真のエラーが返されるコアであることが容易にわかります.1)関数名と行番号を,ユーザエラーメッセージの文字列に追加する.CuStringInsert文によって実現されます.2)エラーフラグ、tc->failedセット.3)完全なエラーメッセージは、テストに割り当てられたメッセージポインタを参照します.4)戻る、長いジャンプ.void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message)
{
CuString string;
CuStringInit(&string);
if (message2 != NULL)
{
CuStringAppend(&string, message2);
CuStringAppend(&string, ": ");
}
CuStringAppend(&string, message);
CuFailInternal(tc, file, line, &string);
}
static void CuFailInternal(CuTest* tc, const char* file, int line, CuString* string)
{
char buf[HUGE_STRING_LEN];
sprintf(buf, "%s:%d: ", file, line);
CuStringInsert(string, buf, 0);
tc->failed = 1;
tc->message = string->buffer;
if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0);
}
ここまで来ると、誤ったテストがlongjmpから返されます.
2.2テストの組織
どんなにすばらしいテストを設計しても、1つ1つの論理テスト関数が必要です.これがテストcaseです.たとえば、次のテストcaseです.測定対象関数のプロトタイプ:int AddInt(int a, int b);
テスト例:void test_add(CuTest* tc)
{
CuAssert(tc, "\r
test not pass", 2 == AddInt(1,0);
}
CuSuite* TestAdd(void)
{
CuSuite* suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_add);
return suite;
}
多くのテストがある場合は、テストグループの管理に使用します.つまりテストcaseの管理で、CuTestではsuiteと呼ばれています.CuSuite* CuGetSuite(void)
{
CuSuite* suite = CuSuiteNew();
SUITE_ADD_TEST(suite, TestCuStringAppendFormat);
SUITE_ADD_TEST(suite, TestCuStrCopy);
SUITE_ADD_TEST(suite, TestFail);
SUITE_ADD_TEST(suite, TestAssertStrEquals);
SUITE_ADD_TEST(suite, TestAssertStrEquals_NULL);
return suite;
}
一般にsuiteはテストの集合であり,実際にはCuSuiteAdd関数が呼び出される.#define SUITE_ADD_TEST(SUITE,TEST) CuSuiteAdd(SUITE, CuTestNew(#TEST, TEST))
マクロで展開すると、TESTはTESTコンテンツが文字列に変換されるのと等価であり、CuTestNew(#TEST,TEST)はマクロの一種の妙用である.この関数の役割は、testSuiteの具体的なチェーンテーブルにcaseを追加することです.void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase)
{
assert(testSuite->count < MAX_TEST_CASES);
testSuite->list[testSuite->count] = testCase;
testSuite->count++;
}
上はsuite関数SUITE_を使ったテストですADD_TESTは、複数のテスト関数の照合管理を実現します.では、複数の関数のテストがある場合、どのように計画されているのか、suiteにsuiteを追加する必要があります.最後に、上位インタフェースに全体的なsuiteの参照を提供すればよい. CuSuite* suite = CuSuiteNew();
CuSuiteAddSuite(suite, CuGetSuite());
CuSuiteAddSuite(suite, CuStringGetSuite());
CuSuiteAddSuite(suite, TestAdd());
2.3テストの実行
テストcaseはテストグループ--suiteを構成し、複数のテストグループを1つのテストグループにまとめることができます.テストグループの実行は配列を遍歴し,内部の各テストcaseを実行する.void CuSuiteRun(CuSuite* testSuite)
{
int i;
for (i = 0 ; i < testSuite->count ; ++i)
{
CuTest* testCase = testSuite->list[i];
CuTestRun(testCase);
if (testCase->failed) { testSuite->failCount += 1; }
}
}
テストの実行はCuTestRunで行い、依然としてジャンプブレークポイントであるsetjmp(buf)を打ってテストcaseを実行し、テストcaseにエラーがなければ静かに終了し、そうでなければエラー情報を記録し、longjmpはif(setjmp(buf)==0)行に戻り、CuSuiteRunではエラーcaseの個数をカウントし、すべてのcaseが実行された後、総括情報を出力する.void CuTestRun(CuTest* tc)
{
jmp_buf buf;
tc->jumpBuf = &buf;
if (setjmp(buf) == 0)
{
tc->ran = 1;
(tc->function)(tc);
}
tc->jumpBuf = 0;
}
上の関数は、テスト関数の呼び出しがあいまいで、(tc->function)(tc)文で完了します.テストcaseのプロトタイプは次のとおりです.typedef void (*TestFunction)(CuTest *);
struct CuTest
{
char* name;
TestFunction function;
int failed;
int ran;
const char* message;
jmp_buf *jumpBuf;
};
だからfunctionは具体的なテストcaseを指します.具体的には、最初のステップでテストcase、すなわちCuTest*tcを作成します.CuTestNewが入力するパラメータfunctionはcase関数を具体的にテストする参照ポインタである.CuTest* CuTestNew(const char* name, TestFunction function)
{
CuTest* tc = CU_ALLOC(CuTest);
CuTestInit(tc, name, function);
return tc;
}
ステップ2では、caseの初期化をテストし、funciton参照ポインタをCuTest*t->functionに割り当てます.したがって(tc->function)(tc)文は、テストcase関数本体を直接呼び出すことに相当する.void CuTestInit(CuTest* t, const char* name, TestFunction function)
{
t->name = CuStrCopy(name);
t->failed = 0;
t->ran = 0;
t->message = NULL;
t->function = function;
t->jumpBuf = NULL;
}
3、CuTestの例
以下に、テストcase、テストグループ、テスト実行を含む簡単な例を示します.1)テストケースvoid test_add(CuTest* tc)
{
CuAssert(tc, "\r
test not pass", 2 == 1 + 1);
}
2)テストグループsuiteCuSuite* TestAdd(void)
{
CuSuite* suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_add);
return suite;
}
3)プロジェクト構造組織のテストvoid main()
{
RunAllTests();
getchar();
}
void RunAllTests(void)
{
CuString *output = CuStringNew();
CuSuite* suite = CuSuiteNew();
CuSuiteAddSuite(suite, TestAdd());
CuSuiteRun(suite);
CuSuiteSummary(suite, output);
CuSuiteDetails(suite, output);
printf("%s
", output->buffer);
}
#define CuAssertStrEquals(tc,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
#define CuAssertStrEquals_Msg(tc,ms,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
#define CuAssertIntEquals(tc,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
#define CuAssertIntEquals_Msg(tc,ms,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
#define CuAssertDblEquals(tc,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac),(dl))
#define CuAssertDblEquals_Msg(tc,ms,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac),(dl))
#define CuAssertPtrEquals(tc,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
#define CuAssertPtrEquals_Msg(tc,ms,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
......
......
void CuAssertIntEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
int expected, int actual)
{
char buf[STRING_MAX];
if (expected == actual) return;
sprintf(buf, "expected but was ", expected, actual);
CuFail_Line(tc, file, line, message, buf);
}
sprintf(buf, "expected but was ", expected, actual);
CuFail_Line(tc, file, line, message, buf);
void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message)
{
CuString string;
CuStringInit(&string);
if (message2 != NULL)
{
CuStringAppend(&string, message2);
CuStringAppend(&string, ": ");
}
CuStringAppend(&string, message);
CuFailInternal(tc, file, line, &string);
}
static void CuFailInternal(CuTest* tc, const char* file, int line, CuString* string)
{
char buf[HUGE_STRING_LEN];
sprintf(buf, "%s:%d: ", file, line);
CuStringInsert(string, buf, 0);
tc->failed = 1;
tc->message = string->buffer;
if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0);
}
int AddInt(int a, int b);
void test_add(CuTest* tc)
{
CuAssert(tc, "\r
test not pass", 2 == AddInt(1,0);
}
CuSuite* TestAdd(void)
{
CuSuite* suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_add);
return suite;
}
CuSuite* CuGetSuite(void)
{
CuSuite* suite = CuSuiteNew();
SUITE_ADD_TEST(suite, TestCuStringAppendFormat);
SUITE_ADD_TEST(suite, TestCuStrCopy);
SUITE_ADD_TEST(suite, TestFail);
SUITE_ADD_TEST(suite, TestAssertStrEquals);
SUITE_ADD_TEST(suite, TestAssertStrEquals_NULL);
return suite;
}
#define SUITE_ADD_TEST(SUITE,TEST) CuSuiteAdd(SUITE, CuTestNew(#TEST, TEST))
void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase)
{
assert(testSuite->count < MAX_TEST_CASES);
testSuite->list[testSuite->count] = testCase;
testSuite->count++;
}
CuSuite* suite = CuSuiteNew();
CuSuiteAddSuite(suite, CuGetSuite());
CuSuiteAddSuite(suite, CuStringGetSuite());
CuSuiteAddSuite(suite, TestAdd());
void CuSuiteRun(CuSuite* testSuite)
{
int i;
for (i = 0 ; i < testSuite->count ; ++i)
{
CuTest* testCase = testSuite->list[i];
CuTestRun(testCase);
if (testCase->failed) { testSuite->failCount += 1; }
}
}
void CuTestRun(CuTest* tc)
{
jmp_buf buf;
tc->jumpBuf = &buf;
if (setjmp(buf) == 0)
{
tc->ran = 1;
(tc->function)(tc);
}
tc->jumpBuf = 0;
}
typedef void (*TestFunction)(CuTest *);
struct CuTest
{
char* name;
TestFunction function;
int failed;
int ran;
const char* message;
jmp_buf *jumpBuf;
};
CuTest* CuTestNew(const char* name, TestFunction function)
{
CuTest* tc = CU_ALLOC(CuTest);
CuTestInit(tc, name, function);
return tc;
}
void CuTestInit(CuTest* t, const char* name, TestFunction function)
{
t->name = CuStrCopy(name);
t->failed = 0;
t->ran = 0;
t->message = NULL;
t->function = function;
t->jumpBuf = NULL;
}
void test_add(CuTest* tc)
{
CuAssert(tc, "\r
test not pass", 2 == 1 + 1);
}
CuSuite* TestAdd(void)
{
CuSuite* suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_add);
return suite;
}
void main()
{
RunAllTests();
getchar();
}
void RunAllTests(void)
{
CuString *output = CuStringNew();
CuSuite* suite = CuSuiteNew();
CuSuiteAddSuite(suite, TestAdd());
CuSuiteRun(suite);
CuSuiteSummary(suite, output);
CuSuiteDetails(suite, output);
printf("%s
", output->buffer);
}