您的位置:首页 > 编程语言 > C语言/C++

c语言实现一个单元测试框架(Unit Test Framework)代码

2012-03-23 22:56 585 查看
csdn lidp  转载注明出处

此单元测试框架为我在google code上的开源项目spider-tool的一部分,

关于spider-tool,欢迎访问google code. 
https://spider-tool.googlecode.com
test_engine.h

/*
* Spider -- An open source C language toolkit.
*
* Copyright (C) 2011 , Inc.
*
* lidp <openser@yeah.net>
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/

/*!
* \brief  Unit Test Framework
*
*/

#ifndef _TEST_ENGINE_H
#define _TEST_ENGINE_H

#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif

#define SPD_TEST_FRAMEWORK

#ifdef SPD_TEST_FRAMEWORK
#define SPD_TEST_INIT(name) static enum spd_test_result name(struct spd_test_record *record, enum spd_test_cmd type, struct spd_test *test)
#define SPD_TEST_REGISTER(name) spd_test_register(name)
#define SPD_TEST_UNREGISTER(name) spd_test_unregister(name)
#define SPD_TEST_REPORT(name, category,file) spd_test_report(name, category,file)
#define SPD_TEST_RUN(name, category) spd_test_run(name, category)
#else
#define SPD_TEST_INIT(name)
#define SPD_TEST_REGISTER(name)
#define SPD_TEST_UNREGISTER(name)
#define SPD_TEST_REPORT(name, category,file)
#define SPD_TEST_RUN(name, category)
#endif

enum spd_test_result {
TEST_RESULT_NOT_RUN,
TEST_RESULT_PASS,
TEST_RESULT_FAILED,
};

enum spd_test_cmd {
SPD_TEST_CMD_INIT,
SPD_TEST_CMD_RUN,
};

struct spd_test_record {
const char *name; /* unique name of this  test */
const char *category; /*  test in this category can be run */
const char *description; /*  a description of test  */
};

struct spd_test;

typedef enum spd_test_result (spd_test_cb_t)(struct spd_test_record *record, enum spd_test_cmd type, struct spd_test* test);

/* register a test record */
int spd_test_register(spd_test_cb_t *cb);

/* unresigster a test record  */
int spd_test_unregister(spd_test_cb_t *cb);

/*
* run test framework  in three mode :
* name : run given name test case, may be NULL
* category : run a class of test of given category , may be NULL
* if both name and category is NULL , will run all test case.
*
*/
int spd_test_run(const char *name, const char *category);

/*!
*\brief report test result in three mode ,by name, by category or all. may write result to textfile.
*/
int spd_test_report(const char *name, const char *category, const char *textfile);

int __spd_test_update_state(const char *file, const char *func, int line , struct spd_test *test,
const char *fmt, ...);

#define spd_test_update_state(test, fmt,...)  __spd_test_update_state(__FILE__, __PRETTY_FUNCTION__, __LINE__, (test), (fmt), ## __VA_ARGS__)

#if defined(__cplusplus) || defined(c_plusplus)
}
#endif

#endif


test_engine.c

/*
* Spider -- An open source C language toolkit.
*
* Copyright (C) 2011 , Inc.
*
* lidp <openser@yeah.net>
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/

#include "test_engine.h"
#include "logger.h"
#include "str.h"
#include "linkedlist.h"
#include "times.h"

/* stand for a  single test record */
struct spd_test {
struct spd_test_record info;   /* store test info */
enum spd_test_result result;   /* test result */
struct spd_str *status_str;    /* test status in string */
unsigned int howlong;  		   /* time this test cost */
spd_test_cb_t *callback;       /* real func perform test work */
SPD_LIST_ENTRY(spd_test)  list;
};

struct test_resultstr{
enum spd_test_result result;
const char* state;
} ;

static const struct test_resultstr test_result2str [] = {
{TEST_RESULT_NOT_RUN, "NOT RUN"},
{TEST_RESULT_PASS, "PASS"},
{TEST_RESULT_FAILED,

"FAILED"},
};

/* time mode */
enum test_type {
TEST_NAME = 0,
TEST_CATEGORY,
TEST_ALL,
};

/*! global structure containing both total and last test execution results */
static struct spd_test_execute_results {
unsigned int total_tests;  /*!< total number of tests, regardless if they have been executed or not */
unsigned int total_passed; /*!< total number of executed tests passed */
unsigned int total_failed; /*!< total number of executed tests failed */
unsigned int total_time;   /*!< total time of all executed tests */
unsigned int last_passed;  /*!< number of passed tests during last execution */
unsigned int last_failed;  /*!< number of failed tests during last execution */
unsigned int last_time;    /*!< total time of the last test execution */
} last_results;

static SPD_LIST_HEAD_STATIC(spd_test_list, spd_test);
static struct spd_test* spd_test_create(spd_test_cb_t*callback);
static struct spd_test* spd_test_free(struct spd_test *test);
static int spd_test_insert(struct spd_test *test);
static struct spd_test* spd_test_remove(spd_test_cb_t* cb);
static int spd_test_category_cmp(const char *cat1, const char *cat2);

int __spd_test_update_state(const char * file, const char * func, int line, struct spd_test * test, const char * fmt,...)
{
struct spd_str *buf = NULL;
va_list ap;

if(!(buf = spd_str_create(128))) {
return -1;
}

va_start(ap, fmt);
spd_str_set_va(&buf, 0, fmt, ap);
va_end(ap);

spd_str_append(&test->status_str,0, "[%s:%s:%d]: %s",
file, func, line,spd_str_buffer(buf));

spd_safe_free(buf);

return 0;
}

int spd_test_register(spd_test_cb_t *cb)
{
struct spd_test *test;

if(!cb) {
spd_log(LOG_WARNING, "No callback be specified\n");
return -1;
}

if(!(test = spd_test_create(cb))) {
spd_log(LOG_WARNING, "failed to create test case\n");
return -1;
}

if(spd_test_insert(test)) {
spd_log(LOG_WARNING, "failed to insert test \n");
spd_test_free(test);
return -1;
}

return -1;
}

int spd_test_unregister(spd_test_cb_t *cb)
{
struct spd_test *test;

if(!cb)  {
spd_log(LOG_WARNING, "No callback be specified\n");
return -1;
}

if(!(test = spd_test_remove(cb))) {
return -1;
}

spd_test_free(test);

return 0;
}

static struct spd_test *spd_test_free(struct spd_test* test)
{
if(!test) {
return NULL;
}

spd_safe_free(test->status_str);
spd_safe_free(test);

return NULL;
}

static struct spd_test *spd_test_create(spd_test_cb_t * callback)
{
struct spd_test *test = NULL;

if(!callback || !(test = spd_calloc(1, sizeof(struct spd_test)))) {
return NULL;
}

test->callback = callback;
test->callback(&test->info, SPD_TEST_CMD_INIT, test);

if(spd_strlen_zero(test->info.category)) {
spd_log(LOG_WARNING, "No category set, cannot register. \n");
return spd_test_free(test);
}

if(spd_strlen_zero(test->info.name)) {
spd_log(LOG_WARNING, "No name set , cannot register. \n");
return spd_test_free(test);
}

if(test->info.category[0] != '/' || test->info.category[strlen(test->info.category) - 1] != '/') {
spd_log(LOG_WARNING, "category %s must start with / and end whith /.\n", test->info.category);
}

if(spd_strlen_zero(test->info.description)) {
spd_log(LOG_WARNING, "NO desc set , cannot register.\n");
return spd_test_free(test);
}

if(!(test->status_str = spd_str_create(128))) {
return spd_test_free(test);
}

return test;

}

static int spd_test_insert(struct spd_test *test)
{
SPD_LIST_LOCK(&spd_test_list);
SPD_LIST_INSERT_SORTALPHA(&spd_test_list, test,list,info.category);
SPD_LIST_UNLOCK(&spd_test_list);

return 0;
}

static struct spd_test* spd_test_remove(spd_test_cb_t *cb)
{
struct spd_test *cur = NULL;

SPD_LIST_LOCK(&spd_test_list);
SPD_LIST_TRAVERSE_SAFE_BEGIN(&spd_test_list, cur, list) {
if(cur->callback == cb) {
SPD_LIST_REMOVE_CURRENT(&spd_test_list, list);
break;
}
}
SPD_LIST_TRAVERSE_SAFE_END;
SPD_LIST_UNLOCK(&spd_test_list);

return cur;
}

static int spd_test_category_cmp(const char *cat1, const char *cat2)
{
int len1 = 0;
int len2 = 0;

if (!cat1 || !cat2) {
return -1;
}

len1 = strlen(cat1);
len2 = strlen(cat2);

if (len2 > len1) {
return -1;
}

return strncmp(cat1, cat2, len2) ? 1 : 0;
}

static int run_one(struct spd_test *test)
{

struct timeval start =  spd_tvnow();

spd_str_reset(test->status_str);

test->result = test->callback(&test->info, SPD_TEST_CMD_RUN, test);

test->howlong = spd_tvdiff_ms(spd_tvnow(), start);

return 0;
}

int spd_test_run(const char * name, const char * category)
{
enum test_type mode = TEST_ALL;
struct spd_test *test = NULL;
int ret = 0;
int run = 0;

if(!spd_strlen_zero(category)) {
if(spd_strlen_zero(name)) {
mode = TEST_NAME;
} else {
mode = TEST_CATEGORY;
}
}

SPD_LIST_LOCK(&spd_test_list);

memset(&last_results, 0, sizeof(last_results));
SPD_LIST_TRAVERSE(&spd_test_list, test, list) {
run = 0;
switch(mode) {
case TEST_CATEGORY:
if(!spd_test_category_cmp(test->info.category, category)) {
run = 1;
}
break;
case TEST_NAME:
if(!spd_test_category_cmp(test->info.category, category) && !strcmp(test->info.name, name)) {
run = 1;
}
break;
case TEST_ALL:
run = 1;
break;
}

if(run) {
spd_log(LOG_DEBUG, "START Unite Test  %s - %s \n",test->info.category, test->info.name);

run_one(test);

last_results.last_time += test->howlong;
if(test->result == TEST_RESULT_PASS) {
last_results.last_passed++;
} else if(test->result = TEST_RESULT_FAILED) {
last_results.last_failed++;
}
}

last_results.total_time += test->howlong;
last_results.total_tests++;
if(test->result != TEST_RESULT_NOT_RUN) {
if(test->result == TEST_RESULT_PASS) {
last_results.total_passed++;
} else {
last_results.total_failed++;
}
}
}

ret = last_results.total_passed + last_results.total_failed;

if(!(last_results.last_passed + last_results.last_failed)) {
spd_log(LOG_WARNING, "No Test Found.\n ");
return ret;
} else {

spd_log(LOG_NOTICE, "Number of Tests:            %d\n", (last_results.last_passed + last_results.last_failed));
spd_log(LOG_NOTICE, "Number of Tests Executed:   %d\n",last_results.total_tests);
spd_log(LOG_NOTICE, "Passed Tests:               %d\n", last_results.last_passed);
spd_log(LOG_NOTICE, "Failed Tests:               %d\n",last_results.last_failed);
spd_log(LOG_NOTICE, "Total Execution Time:       %d\n", last_results.total_time);
}

SPD_LIST_UNLOCK(&spd_test_list);

return ret;
}

static void test_report_one(FILE *txtfile, const struct spd_test *test)
{
if(!txtfile || !test) {
return;
}

fprintf(txtfile, "\nName:           %s\n", test->info.name);
fprintf(txtfile, "Category:         %s\n", test->info.category);
fprintf(txtfile, "Description:      %s\n", test->info.description);
fprintf(txtfile, "Result:           %s\n", test_result2str[test->result].state);

if(test->result != TEST_RESULT_NOT_RUN) {
fprintf(txtfile, "Time:          %d\n",test->howlong);
}

if(test->result == TEST_RESULT_FAILED) {
fprintf(txtfile, "Error Description:  %s\n", spd_str_buffer(test->status_str));
}
}

int spd_test_report(const char * name, const char * category, const char * textfile)
{
enum test_type mode = TEST_ALL;
FILE *ftext;
struct spd_test *test = NULL;

if(!spd_strlen_zero(category)) {
if(spd_strlen_zero(name)) {
mode = TEST_NAME;
} else {
mode = TEST_CATEGORY;
}
}

if(!spd_strlen_zero(textfile)) {
if(!(ftext = fopen(textfile, "w"))) {
spd_log(LOG_ERROR, "cannot open textfile : %s\n", textfile);
return -1;
}
}

SPD_LIST_LOCK(&spd_test_list);

if(ftext) {
fprintf(ftext,"Number of Tests:            %d\n", last_results.total_tests);
fprintf(ftext,"Number of Tests Executed:   %d\n",(last_results.total_passed + last_results.total_failed));
fprintf(ftext,"Passed Tests:               %d\n", last_results.total_passed);
fprintf(ftext,"Failed Tests:               %d\n", last_results.total_failed);
fprintf(ftext,"Total Execution Time:       %d\n", last_results.total_time);
}

SPD_LIST_TRAVERSE(&spd_test_list, test, list) {

switch(mode) {
case TEST_CATEGORY:
if(!spd_test_category_cmp(test->info.category, category)) {
test_report_one(ftext, test);
}
break;
case TEST_NAME:
if(!spd_test_category_cmp(test->info.category, category) && !strcmp(test->info.name, name)) {
test_report_one(ftext, test);
}
break;
case TEST_ALL:
test_report_one(ftext, test);
break;
}
}

SPD_LIST_UNLOCK(&spd_test_list);

if(ftext)
fclose(ftext);

return 0;
}


测试 代码:

enum {
FAMILY = 0,
KEY = 1,
VALUE = 2,
};

SPD_TEST_INIT(test_db)
{
int res = TEST_RESULT_PASS;
int i;
char buf[sizeof(large_name)] = {0,};
const char *inputs[][3] = {
{"family", "key", "value"},
{"dbtest", "a", "b"},
{"dbtest", "a", "a"},
{"dbtest", "b", "a"},
{"dbtest", "b", "b"},
};

switch(type) {
case SPD_TEST_CMD_INIT:
record->name = "test_db";
record->category = "/spider/db/";
record->description = "spd db get|put|del unit test";

return TEST_RESULT_NOT_RUN;

case SPD_TEST_CMD_RUN:
break;
}

for(i = 0; i < ARRAY_LEN(inputs); i++) {

if(spd_db_put(inputs[i][FAMILY], inputs[i][KEY], inputs[i][VALUE])) {
spd_log(LOG_ERROR, "test failed in db put %s : %s : %s : \n", inputs[i][FAMILY], inputs[i][KEY], inputs[i][VALUE]);
spd_test_update_state(test, "test failed in db put %s : %s : %s : \n", inputs[i][FAMILY], inputs[i][KEY], inputs[i][VALUE]);
res = TEST_RESULT_FAILED;
}

if(spd_db_get(inputs[i][FAMILY], inputs[i][KEY], buf, sizeof(buf))) {
spd_log(LOG_ERROR, "test failed in db put %s : %s : %s : \n", inputs[i][FAMILY], inputs[i][KEY], inputs[i][VALUE]);
spd_test_update_state(test, "test failed in db put %s : %s : %s : \n", inputs[i][FAMILY], inputs[i][KEY], inputs[i][VALUE]);
res = TEST_RESULT_FAILED;
} else if (strcasecmp(inputs[i][VALUE], buf)) {
spd_log(LOG_ERROR, "test failed in db get , this is not match value,expect %s but %s \n", buf,inputs[i][VALUE]);
spd_test_update_state(test, "test failed in db get , this is not match value,expect %s but %s \n", buf,inputs[i][VALUE]);
res = TEST_RESULT_FAILED;
} else {
//spd_log(LOG_NOTICE, "get success %s %s %s \n", inputs[i][FAMILY], inputs[i][KEY], buf);
}

if(spd_db_del(inputs[i][FAMILY], inputs[i][KEY])) {
spd_log(LOG_ERROR, "test failed in db del %s : %s : \n", inputs[i][FAMILY], inputs[i][KEY]);
spd_test_update_state(test, "test failed in db get , this is not match value,expect %s but %s \n", buf,inputs[i][VALUE]);
res = TEST_RESULT_FAILED;
}

}

return res;
}

int test_spddb()
{

SPD_TEST_REGISTER(test_db);
SPD_TEST_RUN("test_db",NULL);
SPD_TEST_REPORT("test_db", NULL, "/tmp/spddb_test");
SPD_TEST_UNREGISTER(test_db);

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐