APR中的单元测试框架:深入分析内部实现

APR单元测试框架实现的实在简单,除了断言集之外ABTS只向外部暴露了两个接口:

abts_suite *abts_add_suite(abts_suite *suite, const char *suite_name_full);
void abts_run_test(abts_suite *ts, test_func f, void *value);


#define ADD_SUITE(suite) abts_add_suite(suite, __FILE__);

我们暂时不看函数的实现,先看看几个核心的数据结构。
struct sub_suite {
    const char *name;
    int num_test;
    int failed;
    int not_run;
    int not_impl;
    struct sub_suite *next;
};
sub_suite对应于对一个模块测试的的测试用例集合,他包含测试的一些统计信息,方便以后生成测试报告。

struct abts_suite {
    sub_suite *head;
    sub_suite *tail;
};
这算是所有sub_suite的根了,我们是在它的基础上构建sub_suite链表的。根据它我们可以找到我们最关心的两个suite。head指向第 一个suite,可以方便我们定位整个链表、tail指向最后一个suite,其实定位的是我们当前正在测试的suite。

struct abts_case {
    int failed;
    sub_suite *suite;
};
表示一个测试用例,它不会保存在链表中。被断言集使用,记录断言是否失败。

令人惊叹的是,你在使用这个框架是我们几乎不用关心这3个数据结构,只需要机械的使用ADD_SUITE和abts_run_test 就好了。

下面我们来看看两个主要函数。

1. abts_add_suite

/* Only end the suite if we actually ran it */
if (suite && suite->tail &&!suite->tail->not_run) {
    end_suite(suite);
}


怎么函数刚一开始就要结束掉suite?(end_suite其实是打印suite是否通过测试)仔细分析后才知道,这一句不是要结束自己,而是要结束上一个suite。再回过头来看看主函数中的代码就会明白了。

abts_suite* suite = NULL;
for (i = 0; i < (sizeof(alltests) / sizeof(struct testlist *)); i++) {
    suite = alltests[i].func(suite);
}


让我们一步步的分析:

1. 开始suite = NULL; 第一次当然就不会执行end_suite了,因为这时根本没有前一个suite可以结束掉。

2. 第二次进入循环时,就会调用end_suite了,因为这是suite是第一次执行测试后的返回值,suite != NULL。

3. 依次类推可以得出end_suite实际上是打印前一个测试的结果。这种设计比较的精巧。

再往下看代码,

创建一个subsuite,给这个suite一个名字,这个名字是suite所在的文件的文件名去掉后缀。然后再将其加入到链表中。

2. abts_run_test

这个函数就是调用具体的测试用例函数。测试用例函数是对一个基本功能的测试,它主要使用断言集。

首先取最后一个suite
ss = ts->tail;
即当前正在测试的suite。

然后初始化test_case。并将suite中的测试用例数递增。
tc.failed = 0;
tc.suite = ss;
ss->num_test++;


最后调用实际的测试用例函数,将用例是否失败的信息放入到tc中。更新suite的测试用例失败数。
f(&tc, value);
if (tc.failed) {
    ss->failed++;
}


很简单,我在这里分析好像是没必要的了。我感叹写abts作者的智慧。

你可能感兴趣的:(数据结构,框架,F#,单元测试)