C/C++:测试框架入门之googletest

您所在的位置:网站首页 死亡之种2配置 C/C++:测试框架入门之googletest

C/C++:测试框架入门之googletest

2023-03-03 01:07| 来源: 网络整理| 查看: 265

目录 主要参考资料库介绍安装和部署内置测试机制断言人为干预测试静态类型检查测试用例编写测试固件(test fixture)测试事件 参数化测试更深层次的问题

主要参考资料

Googletest Primer

大佬A(参考了TEST()用法)

大佬B(参考了断言列表)

大佬C(参考了一些细节内容)

库介绍

下面是本菜鸟为了理解框架而做的一点笔记。如果您已经初步了解过googletest,建议跳过此节。

googletest是一个由Google开发的、可在多平台下使用的C++测试框架,允许开发者通过相对优雅的方式组织和运行测试源码,得到稳定可信的测试结果。

与ISTQB等对测试相关概念的定义不同,Google官方在说明文档中将测试(test) 描述为一个检查测试程序内特定值是否符合预期的逻辑单元。而对应的测试用例(test case) 描述一组高关联性测试的组合,测试集(test suite) 则是一组测试目的一致的测试用例的集合。例如:当前需要对一个红黑树类lewis::rbtree进行测试,那么所有测试该类的测试用例可称为一个测试集,所有测试同一个函数/特性的测试,例如所有测试删除元素函数remove(const T&)的测试,可称为一个测试用例。

根据Google官方的说法,一个好的测试应当满足如下特点:

独立且可重复。测试应当不依赖于其他测试而独立运行,方便测试者进行调试。高度组织,可以反映被测试代码的结构。相同或相近目的的测试源码应当组织在同一个测试集或测试用例下。复用性和平台无关性强。否则移植又是一大痛苦。信息丰富。如果测试失败,测试程序应当提供尽可能多的有关测试失败的细节。应当迎合测试者,让测试者的精力集中在测试程序上,而不是让测试者用繁文缛节去迎合测试框架本身。快速。否则每次跑测试都要先让测试大哥冲杯咖啡吗。 安装和部署

googletest可在多数主流平台下安装和部署,但平台下必须已经安装:

CMake、bazel两种构建工具之至少一种至少一套被对应构建工具支持的生成工具链,如MinGW、Visual Studio等

如果你在Windows下,更建议使用vcpkg等第三方库管理器安装部署googletest,如果自行部署,则要额外注意,使用测试库的平台必须和构建该库的平台对应;如果你在Linux下,则可以考虑自行安装部署或者通过包管理器安装部署该库。无论在何种平台下,如果你选择自行安装部署,必须下载一份该库的源码包:google/googletest,再在源码包根目录下执行构建命令。

另外,为了使用googletest,你也必须在使用该库的项目中手动添加引入该库的声明:

# CMake find_package(GTest CONFIG REQUIRED) target_link_libraries(main PRIVATE GTest::gmock GTest::gtest GTest::gmock_main GTest::gtest_main ) 内置测试机制

googletest引入了一系列可用于测试的功能,它们可以相对容易地在源码中使用。

断言

与中的assert(cond)宏类似但不完全相同,googletest的断言是一系列根据测试条件是否满足而做出不同反应的宏。这类宏的统一格式为_,其中ACTION可以为传统断言(ASSERT)或者预期(EXPECT),区别在于ASSERT测试失败将会中止测试用例,EXPECT只会提示测试失败,一般不会干扰测试流程。而CONDITION包括但不限于下列情形之一,关于这些情形对应的断言内容细节,敬请阅读我在文首列出的参考博客。

用途CONDITION格式比较值EQ、NE、GT、GE、LT、LE(val1, val2)判断真假TRUE、FALSE(expr)字符串比对(大小写敏感)STREQ、STRNE(cstr1, cstr2)字符串比对(忽略大小写)STRCASEEQ、STRCASENE(cstr1, cstr2)浮点数比对FLOAT_EQ、DOUBLE_EQ(float1, float2)浮点数近似NEAR(float1, float2, eps)类型异常检查THROW(stmt, except_type)任意异常检查ANY_THROW、NO_THROW(stmt)谓词断言(无格式)PREDk(1 int _fact = 1; for (int i = 1; i // these lines are duplicated intentionally. EXPECT_EQ(Factorial(17), 355687428096000ll); ASSERT_EQ(Factorial(17), 355687428096000ll); // ASSERT :) EXPECT_EQ(Factorial(17), 355687428096000ll); EXPECT_EQ(Factorial(17), 355687428096000ll); }

为了丰富断言测试的输出内容,我们可以利用 public: void Check() { ::testing::StaticAssertTypeEq(); } }; template class FakeAllocator {}; TEST(TypeAssertionTest, Demo) { IsAlloc().Check(); } 测试用例编写

前面已经演示了测试用例的写法,这里说明一下:

TEST(GoodTestSuite, GoodTestCase) { // do some retarded tests >:) }

TEST(suite, case)表示一个测试用例,suite为测试集名称,case为测试用例名称,后面的大括号内是一个测试函数。同一个测试集的所有测试用例相邻执行。

测试程序的入口函数应当添加初始化googletest的有关声明。要运行程序中的所有测试用例,应添加RUN_ALL_TESTS()。

int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } 测试固件(test fixture)

不可否认,上面提到的测试用例和断言机制都很简练。但是,如果各个测试用例之间需要共享一些数据,或者测试用例之间的重复度很高(例如:编译器前端的表达式测试),这个时候编写一堆TEST()显然是不符合测试实际的。

Google提供了“测试固件”(Primer中称其为test fixture,但博客资料大多不提供相关内容,这里提供一个十分蹩脚的译名),用于解决测试用例重复度高、相互依赖性强的问题。为了利用该机制,你需要:

编写一个::testing::Test的派生类,并以protected:开头。因为测试用例的本质是一个测试集的派生,这样可以让所有的测试用例都使用该类定义的内容。在类内声明所有计划在各个测试用例间共享、但不对外公开的成员变量或函数。如果需要,重载SetUp()成员函数和TearDown()成员函数,描述类内成员是如何初始化和销毁的。

下面是一个test fixture的示例:

class StdVectorTestFixture : public ::testing::Test { protected: virtual void SetUp() override { rnd.seed(time(nullptr)); int cnt = 50; while (cnt--) shared_.emplace_back(rnd()); }; virtual void TearDown() override{}; static std::mt19937 rnd; static std::vector shared_; };

注意:对使用同一个fixture的各个测试用例,如果需要在这些用例中间共享数据成员,请将其声明为static变量。因为用例本质上是创建一个派生实例去执行测试,测试完毕后,实例即被销毁,所以非static数据成员不能达到共享数据的目的。Google官方不提倡对这些测试用例共享数据,但如果确需共享,也只能这么做。

test fixture的复用是通过TEST_F(fixture, test)方式实现的,fixture指代fixture的类名,test指代测试用例名。通过TEST_F创建的测试用例可以使用fixture类定义的非private成员。

TEST_F(StdVectorTestFixture, SortingTest) { std::sort(shared_.begin(), shared_.end()); for (size_t idx = 0; idx shared_ = ::operator new(sizeof(size_t) * 120); } static void TearDownTestCase() { delete[] shared_; shared_ = nullptr; } static void* shared_; };

全局事件比较特殊。它不能借助fixture实现,而是需要依赖googletest中的环境(Environment)概念实现。测试程序会在开始测试之前调用所有已注册环境的SetUp()方法,会在全部测试结束后调用TearDown()方法。编写::testing::Environment的派生类,重载这两个方法,可以实现全局级事件。

class TestEnvironmentDemo : public ::testing::Environment { public: virtual void SetUp() { std::cout ::testing::AddGlobalTestEnvironment(new TestEnvironmentDemo); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } 参数化测试

参数化测试着重于解决某些测试用例内部的同质化问题。例如:某些测试用例会使用十数个、数十个或上百个参数,逐个编写将是十分耗时的工作。参数化是一种根据参数列表复制测试用例,从而有效降低带有大量参数的测试用例编写成本的思路,所幸Google的大神也考虑到了这个问题,在googletest中提供了对应的支持。

Google告诉我们,利用参数化功能必须经过三步:

确定参数类型是什么。否则参数化也无从谈起,对吧。定义拿到参数的值之后,做些什么样的测试。这一步应当定义测试用例的样式,指定当用例取得参数时,进行怎样的测试。确定参数的范围,即我们要送哪些参数进测试用例。这一步和上一步的顺序其实无所谓。

参数化测试是由::testing::TestWithParam派生的,必须定义一个对应的派生类,派生类内不需要额外添加内容,但需要通过INSTANTIATE_TEST_SUITE_P(prefix, suite, param_generator)确定使用参数的范围。在类定义的后文通过TEST_P(suite, test)创建使用该参数的测试,同一套参数可以使用于多个测试用例。

class ParamTestDemo : public ::testing::TestWithParam {}; INSTANTIATE_TEST_SUITE_P(ParamList, ParamTestDemo, ::testing::Range(4, 14)); TEST_P(ParamTestDemo, Test1) { int n = GetParam(); EXPECT_GE(1


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3