首先介绍一下TUT Framework下面单元测试用例是如何组织的.
case组织的层级结构(项目(最后是个可运行的二进制程序) >= group >= case )
可以根据主题对用例进行分类, 相同主题的用例可以写到一个group里面,比如所有的对于一个类的测试放在一个group下面.
一个group里面可以包含很多case, 相应的你可以理解为是对一个类的所有成员函数进行测试.
case的内容可以是测试一个函数, 或者函数的某一部分逻辑等等.
那么假设一个某个大工程中使用n个类, 如何在一个单元测试项目里面测试所有的类呢?这里一个可选方案是使用不同的头文件类组织.
也就是说一个头文件里面写一个group的所有相关内容.
最大单位是一个单元测试项目.
group之间可以使用group的名字区分, 或者, 其实group是不同的类型定义, 不会有混淆.
在一个group内,case可以通过id来区分, 一个可选方法是在test函数里面调用set_test_name, 给case一个字符串名称.
TUT主要在5个地方提供了回调接口, 就是说, 在某个事件发生前, 允许你初始化一下, 在某个事件发生后, 允许你做一下统计总结的工作.
这5个地方分别是:
1, 这个项目开始启动的时候,在这里可以记录一下开始运行的时间, 声明一下版本信息之类的.
2, 所有用例运行结束的时候, 这里对一共运行了多少case, 哪个case失败了, 为什么失败, 哪个skip了, 为什么skip, 这都是可以定制性的输出的.
3, 一个用例运行结束, 可以输出一下这个用例的运行状况, 是否失败, 建议对于pass的用例输出内容尽量简略, 以免太多吸引注意力.
4,group启动的时候, 可以声明一下这个group的相关信息
5, group结束, 可以总结一下group内部用例的运行状况.
TUT Framework的运行原理:
TUT单元测试的流程:
单元测试的启动器是test_runner_singleton, 这是一个单件, 仅此而已, 只负责启动整个单元测试的流程.
test_runner是主要控制器, 控制流程的运转, 只需要调用run_tests()函数, 就可以完成运行整个单元测试的流程.
在运行之前, 有两件事要做:
1, 把所有的在头文件里面声明的用例注册进来, 也就是说, test_runner要知道自己需要运行那些用例
2, 把注册回调函数, 对于回调的时机前面介绍了.
对于1, 用例的注册, 其实在test_group(这个类组织前文说的group)的构造函数里就直接注册了.
runner.get().register_group(name_,this);
就是这一句简单的调用, runner是那个单件, 全局可取, 然后调用一下它的register_group就可以把自己注册进group组里面.
对于2, 注册回调函数, 需要自己显式地调用.
tut::runner.get().set_callback(&reporter);
这个回调有很大的灵活性, 因为程序员很可能想定制性地输出一些信息, 这样就可以通过继承原有的callback类来实现了.因为那几个回调函数都是虚函数, 重载一下就ok了.
还有就是可以有多个回调函数, 可以应对某些特殊的需求, 比如多个程序员想要关注的运行状况不同, 或者需要对回调进行分类.
下面就说一下 run_tests()的流程.
1, 调用callback, 所有用例开始之前的回调函数
2,枚举每个注册进来的group, 调用group开始前的回调, 调用group的run_next, 运行这个group里面的所有的case.调用group结束后的回调.
3, 调用callback, 所有用例都结束之后的回调, 总结一下..
结束.
TUT Framework是基于模板机制实现的, 这里介绍一个模板机制使用的两个亮点:
template <int n> void test() { called_method_was_a_dummy_test_ = true; }
1, 使用从模板传入的n来作为测试用例的id, 相互区分. 这样就不用bother 声明一些各种命名的测试函数了.
template <class Data> class test_object : public Data, public test_object_posix
2,这里模板参数传入的Data可以是你要测的那个类, 这样你就可以在test函数里面直接使用类里面的private成员了(如果你有这样的需求的话).
不需要对原有程序的代码进行任何修改.
TUT 和GTest的机制大致相同, 相比之下TUT功能更加简单一些吧, 没有使用宏, 更适合做扩展.