googleTest 源码主线框架性分析

本文备忘一个主题的分析过程和结论,即,googleTest框架中是如何调用相关的测试宏的?

TEST TEST_F TEST_P 等等

1,googleTest 环境与简单示例

1.1 下载 googletest 并编译

下载:

$ git clone https://github.com/google/googletest.git
$ git checkout release-1.10.0

编译:

$ mkdir build
$ cd build/
$ export CXXFLAGS="-Wno-error=maybe-uninitialized"
$ cmake ..
$ make -j
$ ls lib/

 默认为 release,若debug版本则须:

$ cmake .. -DCMAKE_BUILD_TYPE=Debug

成果:

1.2 示例1 验证函数 add

源码

#include <iostream>
#include "gtest/gtest.h"

int add_int_int(int a, int b){
	return a+b;
}

TEST(SumFuncTest, twoNumbers){
	EXPECT_EQ(add_int_int(3,4),7);
	EXPECT_EQ(27, add_int_int(9, 18));
}

GTEST_API_ int main(int argc, char** argv) {
	printf("Running main() from %s\n", __FILE__);
	testing::InitGoogleTest(&argc, argv);
	return RUN_ALL_TESTS();
}

运行:

1.3 示例 2

#include <gtest/gtest.h>

int Foo(int a, int b)
{
    if (a == 0 || b == 0)
    {
        throw "don't do that";
    }

    int c = a % b;

    if (c == 0)
        return b;

    return Foo(b, c);
}


TEST(FooTest, HandleNoneZeroInput)
{
    EXPECT_EQ(2, Foo(4, 10));
    EXPECT_EQ(6, Foo(30, 18));
}

g++ foo.cpp -I ../../googletest/googletest/include -L ../../googletest/build_dbg/lib -lgtest -lgtest_main

编译运行:

1.4 示例3

源码:

#include <iostream>
#include "gtest/gtest.h"


// add_util.cc

float add_from_left(float a, float b, float c, float d,	float e)
{
	float sum = 0.0;
	sum += c;
	sum += a;
	sum += b;
	//sum += c;
	sum += d;
	sum += e;
/*
	sum += a;
	sum += b;
	sum += c;
	sum += d;
	sum += e;
*/
	printf("add_from_left: sum = %f\n", sum);
	return sum;
}


float add_from_right(float a, float b, float c,	float d, float e)
{
	float sum = 0.0;

	sum += e;
	sum += d;
	sum += c;
	sum += b;
	sum += a;

	printf("add_from_right: sum = %f\n", sum);
	return sum;
}

int sum(int a, int b){
	return a+b;
}




TEST(AddFuncTest, floatVSfloat) {
	printf("AddFuncTest: float  sum = %f\n", 1.238f + 3.7f + 0.000353265f + 7898.3f + 12.23209f);
	printf("AddFuncTest: double sum = %f\n", 12.23209 + 7898.3 + 0.000353265 + 3.7 + 1.238);
	EXPECT_EQ(1.238f + 3.7f + 0.000353265f + 7898.3f + 12.23209f, add_from_left(1.238, 3.7, 0.000353265, 7898.3, 12.23209));
	EXPECT_EQ(1.238f + 3.7f + 0.000353265f + 7898.3f + 12.23209f, add_from_right(1.238, 3.7, 0.000353265, 7898.3, 12.23209));
//
}

TEST(AddFuncTest, doubleVSfloat) {
	printf("AddFuncTest: float  sum = %f\n", 1.238f + 3.7f + 0.000353265f + 7898.3f + 12.23209f);
	printf("AddFuncTest: double sum = %f\n", 12.23209 + 7898.3 + 0.000353265 + 3.7 + 1.238);
	EXPECT_EQ(1.238f + 3.7f + 0.000353265f + 7898.3f + 12.23209f, add_from_left(1.238, 3.7, 0.000353265, 7898.3, 12.23209));
	EXPECT_EQ(1.238 + 3.7 + 0.000353265 + 7898.3 + 12.23209, add_from_right(1.238, 3.7, 0.000353265, 7898.3, 12.23209));
//
}

TEST(SumFuncTest, twoNumbers){
	EXPECT_EQ(sum(3,4),7);
	EXPECT_EQ(27, sum(9, 18));
}

GTEST_API_ int main(int argc, char** argv) {
	printf("Running main() from %s\n", __FILE__);
	testing::InitGoogleTest(&argc, argv);
	return RUN_ALL_TESTS();
}

Makefile

EXE := hello_gtest_ex hello_gtest_add_int_int
all: $(EXE)

%: %.cpp
	g++ -O0 -fno-toplevel-reorder $< -o $@ $(INC) $(LD_FLAGS)

INC := -I../googletest/googletest/include/
LD_FLAGS := -L../googletest/build/lib/ -lgtest -lgtest_main


.PHONY: clean
clean:
	-rm -rf $(EXE)

2,示例与源码分析

使用最简单的测试示例,聚焦googletest本身的代码逻辑

观察点,main 函数如何调用到 TEST(...){...} 这种结构中的代码

两种方式互相印证:

方式1,通过编译器的预编译指令 g++ -E ... 生成展开代码;

方式2,通过跟踪源代码,来份些TEST等的展开结果

2.1 TEST

示例代码如上:

simple_gtest.cpp


#include "gtest/gtest.h"

int add_int_int(int a, int b){
	return a+b;
}

TEST(SumFuncTest, twoNumbers){
	EXPECT_EQ(add_int_int(3,4),7);
}

方式1:

g++ -E simple_gtest.cpp -o simple_gtest.i

展开后,simple_gtest.i文件有8W多行,但是其中对我们理解有意义的也就最尾巴上的几行:


int add_int_int(int a, int b){
 return a+b;
}

static_assert(sizeof("SumFuncTest") > 1, "test_suite_name must not be empty"); 
static_assert(sizeof("twoNumbers") > 1, "test_name must not be empty"); 

class SumFuncTest_twoNumbers_Test : public ::testing::Test { 
public: 
        SumFuncTest_twoNumbers_Test() {} 
private:
        virtual void TestBody(); 
        static ::testing::TestInfo* const test_info_ __attribute__ ((unused)); 
        SumFuncTest_twoNumbers_Test(SumFuncTest_twoNumbers_Test const &) = delete; void operator=(SumFuncTest_twoNumbers_Test const &) = delete; 
}; 

::testing::TestInfo* const SumFuncTest_twoNumbers_Test::test_info_ = 
                ::testing::internal::MakeAndRegisterTestInfo( "SumFuncTest", "twoNumbers", nullptr, nullptr, ::testing::internal::CodeLocation("simple_gtest.cpp", 8), (::testing::internal::GetTestTypeId()), ::testing::internal::SuiteApiResolver< ::testing::Test>::GetSetUpCaseOrSuite("simple_gtest.cpp", 8), ::testing::internal::SuiteApiResolver< ::testing::Test>::GetTearDownCaseOrSuite("simple_gtest.cpp", 8), new ::testing::internal::TestFactoryImpl<SumFuncTest_twoNumbers_Test>); 

void SumFuncTest_twoNumbers_Test::TestBody()
{
        switch (0) 
          case 0: 
          default: 
              if (const ::testing::AssertionResult gtest_ar = (::testing::internal::EqHelper::Compare("add_int_int(3,4)", "7", add_int_int(3,4), 7))) 
                  ; 
              else 
                  ::testing::internal::AssertHelper(::testing::TestPartResult::kNonFatalFailure, "simple_gtest.cpp", 9, gtest_ar.failure_message()) = ::testing::Message();
}

分析这段代码会发现,

TEST被展开成为了一个 class SumFuncTest_twoNumbers_Test

它有一个成员函数 TestBody(){....}

观察上述代码中最后一个函数体:void SumFuncTest_twoNumbers_Test::TestBody()

其中出现了被测试的函数等。

这说明,这个函数体中的代码才是是被测试内容,而其外围都是框架。

框架部分只需要把这中类的一个实例添加到某个链表中,然后依次迭代执行每个类的 TestBody成员函数,既可以完成测试任务。

本例中的 class 如下:

通过方法2.来验证一下展开的结果:

第一部分,class 宏

关联TEST宏,我们可以找到如下内容:


#define TEST(test_suite_name, test_name) GTEST_TEST(test_suite_name, test_name)


#define GTEST_TEST(test_suite_name, test_name)             \
  GTEST_TEST_(test_suite_name, test_name, ::testing::Test, \
              ::testing::internal::GetTestTypeId())




// Expands to the name of the class that implements the given test.
#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
  test_suite_name##_##test_name##_Test

// Helper macro for defining tests.
#define GTEST_TEST_(test_suite_name, test_name, parent_class, parent_id)       \
  static_assert(sizeof(GTEST_STRINGIFY_(test_suite_name)) > 1,                 \
                "test_suite_name must not be empty");                          \
  static_assert(sizeof(GTEST_STRINGIFY_(test_name)) > 1,                       \
                "test_name must not be empty");                                \
  class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                     \
      : public parent_class {                                                  \
   public:                                                                     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default;            \
    ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default;  \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete;     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        const GTEST_TEST_CLASS_NAME_(test_suite_name,                          \
                                     test_name) &) = delete; /* NOLINT */      \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) noexcept = delete; \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        GTEST_TEST_CLASS_NAME_(test_suite_name,                                \
                               test_name) &&) noexcept = delete; /* NOLINT */  \
                                                                               \
   private:                                                                    \
    void TestBody() override;                                                  \
    static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;      \
  };                                                                           \
                                                                               \
  ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name,           \
                                                    test_name)::test_info_ =   \
      ::testing::internal::MakeAndRegisterTestInfo(                            \
          #test_suite_name, #test_name, nullptr, nullptr,                      \
          ::testing::internal::CodeLocation(__FILE__, __LINE__), (parent_id),  \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__),          \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__),       \
          new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_(     \
              test_suite_name, test_name)>);                                   \
  void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody()

其中的如下两行:

class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \

: public parent_class { \

TEST 的宏充分展开后,会根据TEST(X,Y) 括号中的X、Y字串定义一个完整的类,并且包含成员函数:
  TestBody()

但是展开的内容中,没有这个函数的函数体。

这个函数体正好就是TEST(X,Y){Z} 中,{Z}的这个部分,即,
 
  TestBody(){Z}

只需要在整个测试系统中,讲上面展开生成的class的一个实例,insert进一个链表中,并依次迭代执行链表的每一对象的成员函数 TestBody(){Z},即可达到测试目的。

第二部分,函数体中的宏

关于 EXPECT_EQ,我们会发现如下定义:

#define EXPECT_EQ(val1, val2) \
  EXPECT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2)

而其中的 EXPECT_PRED_FORMAT2 又被展开为如下:

// Binary predicate assertion macros.
#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \
  GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_)

又 GTEST_PRED_FORMAT2_ 被定义为:

#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure) \
  GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2), on_failure)

而且其中的 GTEST_ASSERT_  被展开为:

#define GTEST_ASSERT_(expression, on_failure)                   \
  GTEST_AMBIGUOUS_ELSE_BLOCKER_                                 \
  if (const ::testing::AssertionResult gtest_ar = (expression)) \
    ;                                                           \
  else                                                          \
    on_failure(gtest_ar.failure_message())

其中 GTEST_AMBIGUOUS_ELSE_BLOCKER_ 展开为:

#define GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
  switch (0)                          \
  case 0:                             \
  default:  // NOLINT

于是得到函数体为:

总之,只需要调用这个 TestBody() 函数,即可完成测试任务。

2.2 TEST_F

2.3 TEST_P

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/843652.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

linux环境安装mongoDB

一、安装单体mogodb 目标&#xff1a;在Linux中部署一个单机的MongoDB&#xff0c;作为生产环境下使用。 提示&#xff1a;和Windows下操作差不多。 步骤如下&#xff1a; &#xff08;1&#xff09;先到官网下载压缩包 mongod-linux-x86_64-4.0.10.tgz 。 &#xff08;2&…

细说MCU用定时器控制ADC采样频率的实现方法并通过Simulink查看串口输出波形

目录 一、硬件工程 二、建立Simulink模型 1.安装MATLAB和Simulink 2.建立Simulink模型 三、代码修改 1.修改回调函数 2.产看结果 3.完整的main.c 本文作者的文章 细说MCU用定时器控制ADC采样频率的实现方法-CSDN博客 https://wenchm.blog.csdn.net/article/details/…

《python语言程序设计》第6章4题反向显示一个整数,使用函数reverse 实现 4位数

* def reverse(n):a n // 1000b n % 1000 // 100c n % 1000 % 100 // 10d n % 100 % 10return str(d) str(c) str(b) str(a)numberT 3456 aText reverse(numberT) print(f"{numberT} is reverse number {aText}")

Spring的IoC与DI介绍

Spring基础 Spring Framework系统架构 Spring Framework是Spring生态圈中最基础的项目,是其它项目的根基。 IoC(Inversion ofControl)控制反转 使用对象时,由主动new产生对象转换为由外部提供给对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。Sprin…

AP ERP与汉得SRM系统集成案例(制药行业)

一、项目环境 江西某医药集团公司&#xff0c;是一家以医药产业为主营、资本经营为平台的大型民营企业集团。公司成立迄今&#xff0c;企业经营一直呈现稳健、快速发展的态势&#xff0c; 2008 年排名中国医药百强企业前 20 强&#xff0c;2009年集团总销售额约38亿元人民币…

监控易V7.6.6.15升级详解15:智能预测管理功能

随着企业IT系统的日益复杂和运维需求的不断提高&#xff0c;传统的监控管理方式已经难以满足现代企业的需求。为了应对这一挑战&#xff0c;监控易系统近期对智能预测管理功能进行了全面升级。本次升级不仅优化了原有功能&#xff0c;还新增了一系列实用特性&#xff0c;旨在为…

openstack设置IP直接登录,不需要加dashboard后缀

openstack 实验环境&#xff0c;openstack-t版&#xff0c;centos2009 修改配置文件 [rootcontroller ~]# vim /WEBROOT /etc/openstack-dashboard/local_settings #将dashboard去掉 WEBROOT /dashboard/ #改为 WEBROOT /[rootcontroller ~]# vim /etc/httpd/conf.d/openst…

交叉编译ethtool(ubuntu 2018)

参考文章&#xff1a;https://www.cnblogs.com/nazhen/p/16800427.html https://blog.csdn.net/weixin_43128044/article/details/137953913 1、下载相关安装包 //ethtool依赖libmul git clone http://git.netfilter.org/libmnl //ethtool源码 git clone http://git.kernel.or…

yolov5进行识别安全帽

进行毕业设计 下载yolov5使用按照教程来进行就行注意事项&#xff08;有必要看看&#xff09;效果 总结 下载yolov5 地址是&#xff1a;https://github.com/ultralytics/yolov5 使用按照教程来进行就行 这里简单说一下&#xff1a; 下载需要的命令&#xff1a; pip install -…

腾讯元宝上线“3D角色梦工厂”:快速生成专属3D角色!

7月16日&#xff0c;腾讯旗下大模型应用“腾讯元宝”上线“3D角色梦工厂”&#xff0c;允许用户通过上传一张五官清晰的正面头像&#xff0c;并选择不同的角色模板&#xff0c;迅速生成个人3D角色&#xff01; 技术特点 “3D角色梦工厂”将大模型生成技术与3D应用相结合&#…

【开发实战】QT5 + OpenCV4 开发环境配置应用演示

前言 作为深度学习算法工程师&#xff0c;必须要掌握应用开发技能吗&#xff1f;搞工程肯定是必须要会界面开发&#xff0c;QT就是一个很不错的选择。本文以QT5.15 OpenCV4.8 OpenVINO2023为例&#xff0c;搭建应用开发环境&#xff0c;演示深度学习模型的QT应用案例。 开发…

提前批不要盲目投!

大家好&#xff0c;我是洋子&#xff0c;最近25届秋招提前批陆续开始了&#xff0c;特别是大厂&#xff0c;比如百度&#xff0c;深信服&#xff0c;OPPO等陆续已经启动提前批招聘 前几天我在群里发了百度的秋招提前批内推码进行内推&#xff0c;今天打开内推系统一看全凉了 …

PACS-医学影像信息管理系统,全影像科室PACS源码,内置包括MPR、CMPR、VR等三维处理功能

PACS系统可以覆盖医院现有放射、CT、MR、核医学、超声、内镜、病理、心电等绝大部分DICOM和非DICOM检查设备&#xff0c;支持从科室级、全院机、集团医院级乃至到区域PACS的平滑扩展&#xff0c;能够与医院HIS、集成平台的有效集成和融合&#xff0c;帮助医院实现了全院医学影像…

园区AR导航系统构建详解:从三维地图构建到AR融合导航的实现

随着现代园区规模的不断扩大与功能的日益复杂&#xff0c;传统的二维地图导航已难以满足访客高效、精准定位的需求。园区内部错综复杂的布局、频繁变更的商户位置常常让访客感到迷茫&#xff0c;造成寻路上的时间浪费。园区AR导航系统以创新的技术手段&#xff0c;破解了私域地…

基于天地图使用Leaflet.js进行WebGIS开发实战

目录 前言 一、天地图的key 1、创建应用 2、调用限制策略 注&#xff1a; &#xff08;1&#xff09;日服务调用超量会暂时拒绝访问&#xff0c;次日自动开启&#xff1b; &#xff08;2&#xff09;如果服务调用过程中存在数据爬取或者下载行为&#xff0c;服务调用会被自…

【BUG】已解决:AttributeError: ‘WindowsPath‘ object has no attribute ‘rstrip‘

AttributeError: ‘WindowsPath‘ object has no attribute ‘rstrip‘ 目录 AttributeError: ‘WindowsPath‘ object has no attribute ‘rstrip‘ 【常见模块错误】 【错误原因】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&…

C语言 打印菱形的程序(Program to print the Diamond Shape)

给定一个数字n &#xff0c;编写一个程序来打印一个有2n行的菱形。 例子 &#xff1a; // C program to print // diamond shape with // 2n rows #include<stdio.h> // Prints diamond // pattern with 2n rows void printDiamond(int n) { int space n - 1; …

用Python实现Cmpp协议的教程

引言&协议概述 &#xff08;CMPP&#xff09;是中国移动为实现短信业务而制定的一种通信协议&#xff0c;用于在客户端&#xff08;SP&#xff0c;Service Provider&#xff09;和中国移动短信网关之间传输短消息&#xff0c;有时也叫做移动梦网短信业务。CMPP3.0是该协议…

在线 PDF 制作者泄露用户上传的文档

两家在线 PDF 制作者泄露了数万份用户文档&#xff0c;包括护照、驾驶执照、证书以及用户上传的其他个人信息。 我们都经历过这样的情况&#xff1a;非常匆忙&#xff0c;努力快速制作 PDF 并提交表单。许多人向在线 PDF 制作者寻求帮助&#xff0c;许多人的祈祷得到了回应。 …

【单目3D检测】smoke(1):模型方案详解

纵目发表的这篇单目3D目标检测论文不同于以往用2D预选框建立3D信息&#xff0c;而是采取直接回归3D信息&#xff0c;这种思路简单又高效&#xff0c;并不需要复杂的前后处理&#xff0c;而且是一种one stage方法&#xff0c;对于实际业务部署也很友好。 题目&#xff1a;SMOKE&…