Cpp练习——MTest测试框架(gtest的模仿实现)
主要参考了Google test(gtest)和知乎上的一篇文章qtest: 一个单元测试库的从头实现以及作者提供的代码,尤其是宏的部分。一直不喜欢也没有学明白宏的各种用法,但是实现这种风格的测试框架也绕不开宏。 在其基础上进行了整理和重构,并且扩展和完善了一些细节的功能。
本文作为 C++ 学习中的小练习,若有疏漏,欢迎指正。
MTest 介绍
MTest 是一个 head-only 的简易框架,只包含两个头文件:
mtest.hpp
负责 MTest 和 MTest::MTestMessage 两个类的实现,不含有任何的宏。mtest_macro.hpp
负责对外提供相应的宏,可以在编译时使用-DUNUSE_MTEST
关闭所有的宏,避免与 gtest 产生冲突。
测试文件需要 include
这两个文件,include顺序无所谓,也可以直接合并成一个文件,但是我个人的喜好是对宏敬而远之,因此单独仍在一个头文件中。主要功能实现在mtest.hpp
,其中不含有任何的宏。
为什么重复造轮子?
- 我希望将 MTest 作为 gtest 的简易替代,尤其是它具有 head-only
的特点:不需要编译和链接相应的库,使用非常轻量,实现细节完全透明。
mtest.hpp
只有五百多行代码,并且代码可读性较高。 - 实现 MTest 也是学习提升的机会:
- 可以学习 gtest 的使用,并且利用简洁的语法完成 gtest 的一个小子集的功能。
- 可以发现语法上的盲点,例如 mtest 利用了全局静态变量在 main
函数之前初始化的特点,将测试函数自动注册,但是这里根据 clang-tidy
的语法检查,需要保证注册时不会抛出任何异常。(
std::string
构造可能抛异常,因此需要避免使用) - 在 filter 的实现中,字符串匹配的判断基于动态规划,测试中发现网上参考代码的小 bug,进行了修正和完善。
- 在保证功能正确的前提下,不断打磨,写出更加干净漂亮,可读性高的代码。
完成内容
已经完成的部分:
- 最基本的
EXPECT_XX
宏和ASSERT_XX
宏; TEST
宏;(没有支持 gtest 的TEST_F
宏以及其它高级用法)- 支持使用 filter
对测试进行过滤筛选,只能使用
c*.1
,?.2
这种简易的 filter,并且只支持一个 filter; - 输出格式和内容基本与 gtest 相同;
- 支持如下的命令行参数:
--mtest_filter=XXX
,设置 filter;--mtest_list_tests
,列出(满足当前 filter 的)所有测试,不执行;--mtest_use_color
,开启彩色输出模式;--mtest_brief
,开启简洁输出模式。
可以继续完善的部分:
TEST_F
宏的实现;- 当前的 filter 支持比较简单,可以进一步实现与 gtest 相同的 filter 规则;
- 使用模板元编程,进一步丰富代码的功能。
MTest模仿gtest,使用如下两种方式提供main函数。
第一种方式是直接在测试文件的合适位置添加宏MTEST_MAIN
(注意避免重复定义main函数),这个宏会自动展开为
1
2
3
4int main(int argc, char *argv[]) {
MTest::InitMTest(argc, argv, __FILE__);
return MTest::RunAllTests();
}
当然在定义了UNUSE_MTEST
时,MTEST_MAIN
宏和其它宏一样都会定义为空,不会产生冲突。
第二种方式是模仿gtest_main的,创建如下文件并编译为一个静态库,在编译测试文件时链接到一起即可
1 |
|
MTest切换gtest
MTest 在实现中尽可能地保持了与 gtest 子集的兼容性:所有 MTest 已经实现的宏和功能都保持了与 gtest 相同的语法,并且可以在不改动测试源文件的前提下,直接从 MTest 切换为 gtest。
从 MTest 切换回 gtest 需要使用编译选项:
-DUNUSE_MTEST
关闭 MTest 提供的宏,它们都定义在mtest_macro.hpp
中;- 由于测试源文件没有包含 gtest
所需的头文件,因此需要使用
-include
编译选项导入头文件,例如
1 | -I../../external/googletest -include gtest/gtest.h |
- 由于测试源文件中没有 main 函数,MTest 使用宏的方式去生成固定的 main
函数,因此改成 gtest
时必须链接
gtest
和gtest_main
这两个库,例如
1 | -L../../external/googletest/lib -lgtest -lgtest_main |
这里编译选项中的路径由 gtest 实际的位置决定。
实例
实例一
这里我们使用 gtest 提供的 sample1 进行展示,略去了文件中的注释内容。sample1 包含两个函数:一个是阶乘,一个是素数判断。
1 | int Factorial(int n) { |
测试文件如下,分别对两个函数进行测试,即两个测试组:FactorialTest
和IsPrimeTest
,各自包含三个测试。
1 | // include ... |
编译运行的结果如下图,这里 gtest 没有使用彩色输出,绿色是 powershell 自带的,MTest 多了一个 logo,输出内容和格式基本相同。
实例二
由于 sample1 只有正确的测试算例,我们再给出一个含有错误测试的例子,测试文件如下
1 |
|
分别使用 MTest 和 gtest 进行编译,得到结果如下,这里 MTest
开启了彩色输出模式--mtest_use_color
。
我们再对两者都开启简洁输出模式,分别使用--mtest_brief
或--gtest_brief
选项,只显示未通过的测试信息。
我们对两者都使用过滤器,分别使用--mtest_filter=*.1
或--gtest_filter=*.1
选项,过滤之后的测试全部通过。
源代码
源文件放置在Github仓库:cpptoybox。