C/C++ 单元自动化测试解决方案总结

目录
  • 前言
  • 一、动机
    • 1.1 方法1:使用正则表达式
    • 1.2 方法2:使用flex/bison 分析c/c++源码文件
    • 1.3 方法3:利用编译已经生成的AST 来生成代码
  • 二、效果展示
    • 2.1 业务代码零修改, 直接使用TU生成边界用例
    • 2.2 使用注解tu::case生成用户自定义用例
    • 2.3 使用注解tu::mock 自动生成mock方法
  • 三、TU实现方案
    • 3.1 AST 是什么?
    • 3.2 AST(Abstract syntax tree)
    • 3.3 方案
  • 四、TU 插件使用的简易程度对比
  • 五、使用TU的优点
  • 六、TU支持的功能
  • 七、总结与展望

前言

C/C++ 开发效率一直被业内开发人员诟病,单元测试开发效率也是如此,以至于开发人员不愿花时间来写单元测试。那么我们是不是可以通过改善编写单元测试的效率来提升项目的测试用例覆盖率?

本文主要介绍如何利用GCC插件来实现提升C/C++开发者的单元效率工具解决方案,希望对大家在提升单元测试效率上有所启发。

一、动机

上图展示了C/C++单元测试的基本流程,在日常开发过程中写单元测试是一项比较大工程量的事情,C/C++ 目前单元测试代码都需要自己手动写,而且对于一些私有方法打桩就更加麻烦。

目前业内无开源的自动化测试框架或者工具,倒是有一些商业的自动测试工具,下图展示了我们自动化测试工具及单元测试库:

即使开源界有gtest等测试库的支持,我们仍然需要编写大量的单元测试用例代码。对于一些private、protected的类方法,编写单元测试用例的效率就更低,需要手动打桩(mock)。同时我们分析测试用例发现,存在很多边界的用例,它们基本上都是很固定或者有一定模式,比如int 最大最小值等。

如何改善编写单元测试的效率,提升C/C++同学开发效率以及程序质量?我们可以通过提取源文件中的函数、类等信息,然后生成对应的单元测试用例。自动生成用例时需要依赖函数的声明、类的声明等信息,那么我们应该如何获取这些信息呢?

例如:如下的函数定义:

void test(int arg) {}

我们希望能够从上面的函数定义中得到函数的返回值类型、函数名称、函数参数类型、函数作用域。通常我们可以通过以下几种方式得到:

1.1 方法1:使用正则表达式

无奈C/C++ 格式比较复杂能够虽然能够使用多种组合来获取对应的函数声明等信息:

void test(int arg){}
void test1(template<template<string>> arg,...){}
void test2(int(*func)(int ,float,...),template<template<string>> arg2){}

那么就需要写一系列的正则表达式:

  • 提取函数名称、参数名:[z-aA-Z_][0-9]+
  • 提取函数返回值:^[a-zA-Z_]

关键词提取出来了,但是他有一个很大的问题:怎么判断文件中书写的代码是符合C/C++语法描述呢?

1.2 方法2:使用flex/bison 分析c/c++源码文件

这当然是一种很好的方式,但是工作量巨大,相当于实现一个具备词法、语法分析器简易版本的编译器,而且要适配不同的语法格式,虽然bison可以解决上述的如何判断语法是否正确问题,但是仍然很复杂。

1.3 方法3:利用编译已经生成的AST 来生成代码

通常我们了解到的GCC编译的过程是以下四个阶段:

源文件->预处理->编译->汇编→链接

但实际上GCC为了支持更多的编程语言、不同的CPU架构做了很多的优化,如下图所示:

上图展示了GCC处理源码及其他优化过程,在前端部分生成的Generic 语言是gcc编译过程中为源码生成的一种与源码语言无关的抽象语法表现形式(AST)。既然GCC编译过程中生成了AST树,那么我们可以通过GCC插件来提取GCC 前端生成的抽象语法树关键信息比如函数返回值、函数名称、参数类型等。总体难度也很高,一方面业内可参考资料很少,只能通过分析GCC的源码来分析AST语法树上的各个节点描述。

本文所描述的自动化生成单元测试用例的解决方案(我们称之为TU:Translate Unit,后文统称为TU)就是基于方法3来实现的,下面我们先来看看我们的自动化测试用例解决方案的效果展示。

二、效果展示

2.1 业务代码零修改, 直接使用TU生成边界用例

在该用例中我们不需要修改任何业务代码就能够为业务代码生成边界测试用例,而且函数参数可边界值实现全排列,大大降低用例遗漏风险。大家可能发现这种没有做任何修改生成的用例是没有断言的,虽然没有断言,它仍然能够帮助发现单元是否会存在边界值引起coredump。

那么如果想要给他加上断言、mock函数,是否没有办法呢?通过C++11 [[]] 新的属性语法,只需要在方法声明或者定义时添加下根据TU的格式添加断言即可,对业务逻辑无侵入。

2.2 使用注解tu::case生成用户自定义用例

很多情况下默认生成的边界测试用例还不能覆盖到核心逻辑,所以我们也提供tu::case 来给用户自定义自己的测试用例及断言。比如有一个int foo (int x,long y) 方法,现在想新增一个测试用例返回值123,函数实参1,1000,那么只要在函数声明前加入,以下代码即可:

[[tu::case("NE","123","1","1000")]]

2.3 使用注解tu::mock 自动生成mock方法

开发过程中我们也常需要对某个方法进行mock(即对原有方法设置一个临时代替方法并且调用方式保持一致),比如某个函数访问Redis、DB这种情况下进行单元测试往往需要对这些方法进行mock,方便其他函数调用进行单元测试,为了方便进行单元测试我们往往会对其进行mock,所以为了方便开发人员进行快速的mock,所以我们提供了tu::mock 的注解帮助开发同学快速的定义注解,然后TU会自动生成对应的mock函数。例如:现在给foo_read 方法mock一个函数,让mock的函数返回10:

三、TU实现方案

3.1 AST 是什么?

GENERIC、GIMPLE和RTL三者构成了gcc中间语言的全部,它们以GIMPLE为核心,由GENERIC承上,由RTL启下,在源文件和目标指令之间的鸿沟之上构建了一个三层的过渡。

GCC在语法分析过程中,所有识别出来的语言部件都用一个叫TREE的变量保存着。这个TREE就是GCC语法树(AST),这个过程叫做GENERIC。实际上它也是GCC的符号表,因为变量名、类型等等这些信息都由TREE关联起来。

下面我们通过gcc编译选项来看下gcc的ast表现形式:

3.2 AST(Abstract syntax tree)

GCC 可以通过添加编译选项-fdump-tree-all 来生成ast 树,ast树文件内容如下:

AST 各个类型描述可以参考:gcc.gnu.org/onlinedocs/…

虽然上图中简单看下一下可以发现,gcc这种表现形式节点与节点之间还存在依赖,比较难于理解,没有clang生成的直观更容易阅读。虽然不利于阅读,但是不影响通过编码来提取AST信息。

3.3 方案

如上图所示,我们通过使用不同的插件收集被测试源文件的AST信息、头文件信息、函数注解(属性),将这些重要信息保存起来。

GCC将用户注册插件事件保存到数组中:

然后在编译构建过程中到就会去查找对应的事件有没有设置回调方法如果设置则进行调用,TU主要使用以下几种插件:

  • PLUGIN_INCLUDE_FILE 用于获取当前文件的所包含的头文件
  • PLUGIN_OVERRIDE_GATE 用户获取普通函数、类
  • PLUGIN_PRE_GENERICIZE 用于获取模板函数的具现化
  • PLUGIN_ATTRIBUTES 用于实现自定义属性或者注解(tu::case\tu::mock ....)

GCC 支持的所有插件类型如下图所示:(摘自gcc 6.3.0 源码)

四、TU 插件使用的简易程度对比

如果仅仅只是做边界测试那么仅需要修改构建的脚本比如cmake 添加对应的插件参数即可。

五、使用TU的优点

  • 接入简单、边界单元测试可以做到业务代码0修改
  • 函数参数可边界值实现全排列,大大降低用例遗漏风险、减少大量重复性的工作
  • 快速生成用户自定义用例、mock方法等

六、TU支持的功能

七、总结与展望

1、文章中对比了三种方法自动生成测试用例的方法,下面对这几种方法进行对比:

2、文章中还主要介绍了TU的功能特点以及基于GCC-AST的实现自动生成测试用例的解决方案。

TU解决方案目前在构建时能够自动生成测试用例已经极大降低了单元测试门槛提升单元测试覆盖率,未来我们也希望能够把TU与IDE相结合,探索更高效便捷的使用方式,通过更加便捷的方式生成指定方法的测试用例。比如通过在函数、方法上,通过快捷键生成当前方法的测试用例等。

到此这篇关于C/C++ 单元自动化测试解决方案总结的文章就介绍到这了,更多相关 C++ 单元测试内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++关于类结构体大小和构造顺序,析构顺序的测试详解

    目录 总结 #include <iostream> using namespace std; /** 1. c++的类中成员若不加修饰符的话,默认是private 2. 调用构造函数时,先递归调用最顶级的父类构造函数,再依次到子类的构造函数. 3. 调用析构函数时相反,先调用最底层的子类析构函数,再依次到父类的构造函数. 4. 空类的sizeof(A)大小为1,多个空类继承后的子类大小也是1 */ class A{ public: A() { cout<<"A const

  • c++的glog与spdlog的性能对比测试分析

    目录 问题: 测试内容: 测试环境: glog测试代码如下: spdlog异步测试代码: 普通io流写入测试代码: 总结: 问题: 之前看到有的博文说glog性能很好,效率很高,当时第一反应是“这个结论是几几年的?”,可惜博文都是各种抄袭和转载,不容易找到结论出处,我一直很怀疑它的写入吞吐性能. 之前作为学习优秀的代码案例,略看过glog的源代码.它是线程同步的方式记录和写入,每次调用日志的地方都要创建和释放日志器,确实在每次创建对象时并没有创建额外缓存空间,而是复用第一次创建的内存空间,这相比

  • google c++程序测试框架googletest使用教程详解

    目录 什么是googletest? googletest简介 谁在使用 GoogleTest? 相关开源项目 googletest的下载与编译 cmake gui编译 在vs2019中使用googletest GTest的一些基本概念 GTest的断言 事件机制 参数化 什么是googletest? googletest简介 ​GoogleTest 是 Google 的 C++ 测试和模拟框架,可以帮助程序员测试C++程序的结果预期,GoogleTest 的代码用cmake管理,可以使用cmak

  • 浅谈c++性能测试工具google benchmark

    一.测试对象 这次测试的对象是标准库的vector,我们将会在vs2019 16.10和Linux + GCC 11.1上进行测试.为了代码写着方便,我还会启用c++17支持. 这次的疑问来自于<A Tour of C++>这本书,最近在重新翻阅本书的时候发现书里第九章给出了一个很有意思的建议:尽量少用reserve方法. 我们都知道reserve会提前分配足够大的内存来容纳元素,这样在push_back时可以减少内存分配和元素移动的次数,从而提高性能.所以习惯上如果我们知道vector中存储

  • C++中CSimpleList的实现与测试实例

    本文实例讲述了C++简单列表类的实现方法.分享给大家供大家参考.具体方法如下: _AFXTLS.CPP文件如下: //#include "StdAfx.h #include <stddef.h> #include <stdio.h> #include "_AFXTLS_.H" struct MyThreadData{ MyThreadData* pNext; int nShortData; }; void CSimpleList::AddHead(vo

  • 基于C++执行内存memcpy效率测试的分析

    在进行memcpy操作时,虽然是内存操作,但是仍然是耗一点点CPU的,今天测试了一下单线程中执行memcpy的效率,这个结果对于配置TCP epoll中的work thread 数量有指导意义.如下基于8K的内存快执行memcpy, 1个线程大约1S能够拷贝500M,如果服务器带宽或网卡到上限是1G,那么网络io的work thread 开2个即可,考虑到消息的解析损耗,3个线程足以抗住硬件的最高负载. 在我到测试机器上到测试结果是: Intel(R) Xeon(R) CPU          

  • C++11中std::move、std::forward、左右值引用、移动构造函数的测试问题

    关于C++11新特性之std::move.std::forward.左右值引用网上资料已经很多了,我主要针对测试性能做一个测试,梳理一下这些逻辑,首先,左值比较熟悉,右值就是临时变量,意味着使用一次就不会再被使用了.针对这两种值引入了左值引用和右值引用,以及引用折叠的概念. 1.右值引用的举例测试 #include <iostream> using namespace std; ​ //创建一个测试类 class A { public: A() : m_a(55) { } ​ int m_a;

  • 详解c++种gmock单元测试框架

    随着微服务和CI的流行,在目前的软件工程领域中单元测试可以说是必不可少的一个环节,在TDD中,单元测试更是被提高到了一个新的高度.但是很多公司由于很多不同的原因,没有能持续维护,或者干脆就从来没有写过单元测试,确实,单元测试在初期和代码维护期会需要花一些投入,但是,如果一个项目是需要长期维护和更新的,那么单元测试的作用,相对于投入来说就根本不算什么.见过很多人写的单元测试,虽然也可以运行,也有覆盖率,但是稍微分析一下就会看出来,那根本就不是单元测试,而已经是集成测试,比如有人竟然要在单元测试中访

  • 浅谈c++性能测试工具之计算时间复杂度

    google benchmark已经为我们提供了类似的功能,而且使用相当简单. 具体的解释在后面,我们先来看几个例子,我们人为制造几个时间复杂度分别为O(n), O(logn), O(n^n)的测试用例: // 这里都是为了演示而写成的代码,没有什么实际意义 static void bench_N(benchmark::State& state) { int n = 0; for ([[maybe_unused]] auto _ : state) { for (int i = 0; i <

  • C/C++ 单元自动化测试解决方案总结

    目录 前言 一.动机 1.1 方法1:使用正则表达式 1.2 方法2:使用flex/bison 分析c/c++源码文件 1.3 方法3:利用编译已经生成的AST 来生成代码 二.效果展示 2.1 业务代码零修改, 直接使用TU生成边界用例 2.2 使用注解tu::case生成用户自定义用例 2.3 使用注解tu::mock 自动生成mock方法 三.TU实现方案 3.1 AST 是什么? 3.2 AST(Abstract syntax tree) 3.3 方案 四.TU 插件使用的简易程度对比

  • 分享4个方便且好用的Python自动化脚本

    目录 前言 1.自动化阅读网页新闻 2.自动生成素描草图 3.自动发送多封邮件 4.自动化数据探索 5.给大家分享一下自动化测试工具 总结 前言 相比大家都听过自动化生产线.自动化办公等词汇,在没有人工干预的情况下,机器可以自己完成各项任务,这大大提升了工作效率. 编程世界里有各种各样的自动化脚本,来完成不同的任务. 尤其Python非常适合编写自动化脚本,因为它语法简洁易懂,而且有丰富的第三方工具库. 这次我们使用Python来实现几个自动化场景,或许可以用到你的工作中. 1.自动化阅读网页新

  • python自动化测试三部曲之unittest框架的实现

    终于等到十一,有时间写博客了,准备利用十一这几天的假期把这个系列的博客写完 该系列文章本人准备写三篇博客 第一篇:介绍python自动化测试框架unittest 第二篇:介绍django框架+request库实现接口测试 第三篇:介绍利用Jenkins实现持续集成 今天进入第一篇,unittest框架介绍 一.unittest简述 unittest是python语言的单元测试框架,在python的官方文档中,对unittest单元测试框架进行了详细的介绍,感兴趣的读者可以到https://www

  • JS表格组件BootstrapTable行内编辑解决方案x-editable

    前言:之前介绍bootstrapTable组件的时候有提到它的行内编辑功能,只不过为了展示功能,将此一笔带过了,罪过罪过!最近项目里面还是打算将行内编辑用起来,于是再次研究了下x-editable组件,遇到过一些坑,再此做个采坑记录吧!想要了解bootstrapTable的朋友可以移步JS组件系列--表格组件神器:bootstrap table. 一.x-editable组件介绍 x-editable组件是一个用于创建可编辑弹出框的插件,它支持三种风格的样式:bootstrap.Jquery U

  • Asp.net_Table控件の单元格纵向合并示例

    业务需要,动态生成表,同一列中数据相同的单元格需要合并. 解决方案,创建Table控件处理类,代码如下: 复制代码 代码如下: /// <summary>表格控件相关操作类 /// </summary> public static class aspTable { /// <summary>合并行 /// </summary> /// <remarks>版权信息:http://www.qqextra.com,http://t.qq.com/ls_

  • 关于DDD:管理"工作单元实例"的两种模式的使用方法

    图如下: 在常见的用例场景下,类图的对象图如下: 问题在一个用例执行过程中,如何保证同一个界限上下文内的所有仓储实例可以共享同一个工作单元实例?解决方案1 仓储采用依赖注入模式 + 使用IOC管理工作单元的生命周期(PerRequest或其它). 代码示例 复制代码 代码如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.T

  • Android中Listview点击item不变颜色及设置listselector 无效的解决方案

    这是同一个问题,Listview中点击item是会变颜色的,因为listview设置了默认的listselector,有一个默认的颜色,同理如果点击没颜色变化我们怎么设置listselector也不会变颜色的. 但是在我们的开发过程中,我们可能会碰到这样的问题listview点击不变颜色,总结了一下大概有这几种原因: 1.item的layout设置background颜色值,去掉背景颜色即可 2.listview中listselector属性的效果被覆盖了,比如列表的Item为一个占满单元格的I

  • 开源MySQL高效数据仓库解决方案:Infobright详细介绍

    Infobright是一款基于独特的专利知识网格技术的列式数据库.Infobright是开源的MySQL数据仓库解决方案,引入了列存储方案,高强度的数据压缩,优化的统计计算(类似sum/avg/group by之类),infobright 是基于mysql的,但不装mysql亦可,因为它本身就自带了一个.mysql可以粗分为逻辑层和物理存储引擎,infobright主要实现的就是一个存储引擎,但因为它自身存储逻辑跟关系型数据库根本不同,所以,它不能像InnoDB那样直接作为插件挂接到mysql,

  • SQL Server数据库安装时常见问题解决方案集锦

    本文我们总结了几个在安装SQL Server数据库时常见问题的解决方案,供初学者学习参考,接下来让我们来一起看一下吧. 常见问题一: 安装Sql Server 2000时出现"以前进行的程序创建了挂起的文件操作,运行安装程序之前,必须重新启动计算机" ,重启后仍然无效. 解决方案: 1.不用退出Sql Server 2000安装程序,直接切换到桌面. 2.打开注册表编辑器(在"运行"中敲入"regedit"之后回车即可),定位到注册表的HKEY_

  • 在Python编程过程中用单元测试法调试代码的介绍

    对于程序开发新手来说,一个最常见的困惑是测试的主题.他们隐约觉得"单元测试"是很好的,而且他们也应该做单元测试.但他们却不懂这个词的真正含义.如果这听起来像是在说你,不要怕!在这篇文章中,我将介绍什么是单元测试,为什么它有用,以及如何对Python的代码进行单元测试. 什么是测试? 在讨论为什么测试很有用.怎样进行测试之前,让我们先花几分钟来定义一下"单元测试"究竟是什么.在一般的编程术语中,"测试"指的是通过编写可以调用的代码(独立于你实际应用

随机推荐