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作者的智慧。