1. Introduction
Cutest is a tiny C-Language Unit test box, one of the most concise test frameworks I've seen so far, with only 2 files, cutest.c and CuTest.h, all of which add up to less than 1000 lines of code. Perfectly formed, test construction, test management, test statements are all included.
2, cutest analysis
2.1 Assertions
Whether a test case is implemented by falling into code is a comparison between the test value and the expected value, which is used to assert.
#defineCuassertstrequals (TC,EX,AC) cuassertstrequals_linemsg ((TC), __file__,__line__,null, (ex), (AC))#defineCuassertstrequals_msg (TC,MS,EX,AC) cuassertstrequals_linemsg ((TC), __file__,__line__, (ms), (ex), (AC))#defineCuassertintequals (TC,EX,AC) cuassertintequals_linemsg ((TC), __file__,__line__,null, (ex), (AC))#defineCuassertintequals_msg (TC,MS,EX,AC) cuassertintequals_linemsg ((TC), __file__,__line__, (ms), (ex), (AC))#defineCuassertdblequals (TC,EX,AC,DL) cuassertdblequals_linemsg ((TC), __file__,__line__,null, (ex), (AC), (DL))#defineCuassertdblequals_msg (TC,MS,EX,AC,DL) cuassertdblequals_linemsg ((TC), __file__,__line__, (ms), (ex), (AC), (DL))#defineCuassertptrequals (TC,EX,AC) cuassertptrequals_linemsg ((TC), __file__,__line__,null, (ex), (AC))#defineCuassertptrequals_msg (TC,MS,EX,AC) cuassertptrequals_linemsg ((TC), __file__,__line__, (ms), (ex), (AC))
......
......
Take the digital test as an example cuassertintequals, in fact, is now:
voidCuassertintequals_linemsg (cutest* TC,Const Char* File,intLineConst Char*message,intExpected,intActual) { CharBuf[string_max]; if(expected = = Actual)return; sprintf (BUF,"expected <%d> but is <%d>", expected, actual); Cufail_line (TC, file, line, message, buf);}
If the test succeeds, the next step will be quiet and return this function from return.
Most of the testing framework philosophy and Linux philosophy is very similar, small is beautiful, less is good, no exception will not disturb the user.
In the event of an error, the error message is saved, as well as the file path/filename/function name, and line number.
" expected <%d> but is <%d> " , expected, actual); Cufail_line (TC, file, line, message, buf);
Further down, the above function is implemented: Stitching the error message to a string and passing it to the Cufailinternal function. It is easy to find from the Cufailinternal function name that this function is the core of the real error return.
1) Append the function name and line number to the string after the user error message. Implemented by the Custringinsert statement.
2) Error flag, tc->failed position.
3) The complete error message refers to the message pointer assigned to the test.
4) return, long jump.
voidCufail_line (cutest* TC,Const Char* File,intLineConst Char* Message2,Const Char*message) {custringstring; Custringinit (&string); if(Message2! =NULL) {Custringappend (&string, Message2); Custringappend (&string,": "); } custringappend (&string, message); Cufailinternal (TC, file, line,&string);}Static voidCufailinternal (cutest* TC,Const Char* File,intLine, custring*string){ CharBuf[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);}
Here, a wrong test is returned from LONGJMP.
2.2 Tested organizations
No matter how subtle a test is, a logical test function is required, which is the test case. For example, the following test case.
Prototype of the function to be tested:
int AddInt (intint b);
Test Case:
void test_add (cutest* tc) { "\r\ntest not pass"2 = = AddInt ( 1,0);} Cusuite* Testadd (void) { cusuite* Suite = cusuitenew (); Suite_add_test (suite, test_add); return Suite;}
If there are many tests, the management of the test group is used. This is the management of test case, called Suite in cutest.
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;}
In general, Suite is a collection of tests that actually call the Cusuiteadd function.
#define Suite_add_test (suite,test) cusuiteadd (SUITE, Cutestnew (#TEST, TEST))
Expand with a macro, #TEST等价于TEST内容转换为字符串, Cutestnew (#TEST, TEST) is a magical use of macros. This function is to add the case to the specific list of testsuite.
void Cusuiteadd (cusuite* testSuite, cutest *testCase) { assert (testSuite->count < max_test_cases); TestSuite->list[testsuite->count] = testCase; TestSuite->count++;}
The above is a class of tests, using the Suite function suite_add_test to achieve the collation management of multiple test functions. So when you have multiple functions, how do you plan to do it, and you need to add your suite to your suite. Finally, a reference to the upper interface is provided for a total suite.
cusuite* Suite = cusuitenew (); Cusuiteaddsuite (Suite, Cugetsuite ()); Cusuiteaddsuite (Suite, Custringgetsuite ()); Cusuiteaddsuite (Suite, Testadd ());
2.3 Run of the test
The test case forms the test group--suite, and then multiple test groups can be combined into one test group. The execution of a test group is to iterate through the array and execute each test case inside.
void Cusuiterun (cusuite* testSuite) { int i; for 0 ; i < Testsuite->count; + +i) { cutest* testCase = testsuite->list[i]; Cutestrun (testCase); if 1 ; } }}
The execution of the test is done by Cutestrun, which is still to lay the jump breakpoint--setjmp (BUF), and then run the test case, if there is no error in the test, exit silently, otherwise log an error message, and then longjmp return to if (setjmp (buf) = = 0 A row, in the Cusuiterun, will count the number of error case, so that after all case runs, the output summary information is used.
void Cutestrun (cutest* tc) { jmp_buf buf; TC->jumpbuf = &buf; if 0 ) { tc1; (TC,function) (TC); } TC0;}
The above function, the call of the test function is very obscure, is the (tc->function) (TC) statement completed. The prototype of the test case is:
void (*testfunction) (cutest *); struct cutest{ Char* name; testfunction function; int failed; int ran; Const Char* message; *jumpbuf;};
So the function points to the specific test case.
The specific implementation is: The first step is to create a test case, the cutest* TC. The parameter function passed in cutestnew is a reference pointer to the specific test case functions.
cutest* cutestnew (constChar* name, testfunction function) { cutest* tc = Cu_alloc ( cutest); Cutestinit (TC, name, function); return TC;}
The second step is to test case initialization and assign the Funciton reference pointer to cutest* t->function. So the (tc->function) (TC) statement is equivalent to calling the test case function body directly.
void Const Char* name, testfunction function) { t->name = custrcopy (name); T0; T0; T->message = NULL; T->function = function; T->jumpbuf = NULL;}
3. Cutest instances
Here is a simple example that includes a test case, a test group, and a test execution.
1) test Case
void test_add (cutest* tc) { "\r\ntest not pass"21 1);}
2) test Group Suite
cusuite* Testadd (void) { cusuite* Suite = cusuitenew (); Suite_add_test (suite, test_add); return Suite;}
3) test Project Structure organization
void Main () { runalltests (); GetChar ();} void Runalltests (void) { *output = custringnew (); Cusuite* Suite = cusuitenew (); Cusuiteaddsuite (Suite, Testadd ()); Cusuiterun (suite); Cusuitesummary (suite, output); Cusuitedetails (suite, output); printf ("%s\n", output->buffer);}
C Language Unit Test framework--cutest