研效优化实践:聊聊单元测试那些事儿单测用例调用代码白盒

作者:ciuwaalu,腾讯安全平台部后台开发

研发效能提升是一个系统化的庞大工程,它涵盖了软件交付的整个生命周期,涉及到产品、架构、开发、测试、运维等各个环节。而单元测试作为软件中最小可测试单元的检查验证环节,可以说是这个庞大工程中最细致但又不可忽视的一个细节因素。本文内容梳理自安全平台部测试效能提升的经验实践,从零开始介绍探讨单测的方法论和优化思路,期望为大家带来参考,欢迎共同交流。什么是单元测试?

在最开始,我们先看看大家认为的单元测试是什么:

以上这些定义为了严谨起见,都是长长的一大段。在这里,我们结合工程实践经验,给出一个“太长不看”版的定义,这个定义不太严谨但更为简单:

开发同学在编码阶段以函数方法为粒度编写测试用例,检验代码逻辑的正确性。

在这个一句话定义里,有四个核心要素:

结合测试V型图,可以清晰看到单元测试在项目周期中所处的位置阶段。

我们不打算罗列《单元测试的N大优势》《写单元测试的N大好处》,只说一条最核心的:单元测试可以尽早发现编码中的低级错误。

越早发现问题,也越容易解决问题。很显然:

来自微软的数据,不同测试阶段发现BUG的平均耗时,供参考:

低级错误造成重大损失的例子实在太多了。有了单元测试,可以避免面向运气开发,面向回滚发布,打破“不知道有没有BUG~上线出事回滚~紧急修复~代码质量逐渐劣化~不知道有没有新BUG”的恶性循环。

黑盒与白盒

在软件测试理论中,常常将被测试对象视为一个盒子,这个神秘的盒子接受一些输入,并做某些处理工作,产生特定的输出结果。

在构造输入数据进行测试时:

白盒测试一般只在单元测试中使用,黑盒测试在单元测试、集成测试等各个阶段都可以使用。

我们以下方这个函数为例子,看看单元测试中如何应用黑盒与白盒测试。首先需要明确,设计单元测试,我们肯定是知道这个函数的具体用途、输入参数和返回结果的含义(即知道盒子的用途):

//从IPv4报文中提取源IP地址uint32_tGetSrcAddrFromIPv4Packet(constvoid*buffer,size_tsize);

如果我们手上只有编译好的二进制库文件,不知道函数的内部实现方式,通过想象这个函数在上线后会遇到什么类型的输入,设计了一些合法和非法的IP报文来做验证,此时是黑盒测试。

如果我们手上有函数源代码,一边看着函数实现,一边根据代码里的分支、逻辑构造各种输入,此时是白盒测试:

比如看到函数内部的if(buffer==nullptr)return-1;设计了一个空缓冲区的用例;

比如看到函数内部的if(size

在大部分情况下,我们是自己给自己写的函数做单元测试,当运用黑盒测试的思路时,要假装被测函数是别人写的。

覆盖

在单元测试中,覆盖率是一个常用的评估指标。

所谓覆盖,可以简单理解为“被执行过”。具体来说:在某个测试用例中,执行了某行代码,则可以说这行代码“被覆盖”;同样,当某个分支的真/假条件都被取到时,则可以说这个分支“被覆盖了”。

常见的覆盖可以分为这几种:

假设我们有一个这么一个待测函数:

intfoo(inta,intb,intc,intd){intresult=0;if(a&&b)//分支1result+=a;if(c||d)//分支2result+=c;returnresult;

语句覆盖是指每条语句都被执行一次。当输入a=1,b=1,c=1,d=1一组用例时可以达到。

分支覆盖是指每个分支真/假条件都被执行一次。当输入a=1,b=1,c=1,d=1以及a=0,b=0,c=0,d=0两组用例时可以达到。

条件覆盖是指每个分支的条件组合方式都被执行一次。当输入a=1,b=1,c=1,d=1(真真)、a=1,b=0,c=1,d=0(真假)、a=0,b=1,c=0,d=1(假真)、a=0,b=0,c=0,d=0(假假)四组用例时可以达到。

语句覆盖是最容易达到、也是最弱的覆盖方式。在工程实践中,考虑到测试成本及测试效果,分支覆盖的覆盖率是最常使用的考察指标。

桩与驱动

假设我们还有这么一个待测函数:

voidfoo(inta){if(a>0){A();}else{B();

foo()调用了外部函数A()B()。

假设A()是一个很重的函数(操作DB、文件或者网络通信……),进行单元测试时,我们不希望引入这些外部依赖,而是希望调用A()时立即返回一些提前准备好的“假数据”,这时需要“仿冒”一个A(),这个伪造过程就叫做插桩,假冒的A()就称为桩函数(stub)。

在做测试时,需要写一个函数来调用foo(),这个调用者就是驱动(driver)。

单元测试简单实践一个简单的单元测试

一个单元测试用例至少包含:

一个简单但完整的单元测试看起来会是这样的:

//待测函数intadd(inta,intb){returna+b;

//测试用例voidTestAdd(){//被测对象预期输出//||||assert(add(1,2)==3);//||||||||//断言输入数据}

//执行测试intmain(){TestAdd();}Given-When-Then

单元测试中被测函数、断言、输入数据、预期输出几个要素,可以通过经典模板Given-When-Then(GWT)来做一些严谨的描述。

使用GWT来描述上一节的用例:

assert(add(//When-测试过程发生的行为-调用被测函数add()1,2//Given-测试前置条件和初始状态-用例输入参数==3//Then-测试结束断言输出结果-断言预期输出

有些现代化的测试框架(例如catch2)对GWT描述做了表达上的优化。下方粘贴了一段单元测试代码示例,有对GWT更为具体的描述:

SCENARIO("vectorscanbesizedandresized","[vector]"){GIVEN("Avectorwithsomeitems"){std::vectorv(5);

REQUIRE(v.size()==5);//REQUIRE()即assert()REQUIRE(v.capacity()>=5);

WHEN("thesizeisincreased"){v.resize(10);

THEN("thesizeandcapacitychange"){REQUIRE(v.size()==10);REQUIRE(v.capacity()>=10);}}WHEN("thesizeisreduced"){v.resize(0);

THEN("thesizechangesbutnotcapacity"){REQUIRE(v.size()==0);REQUIRE(v.capacity()>=5);}}}}组织结构

原则:单元测试尽可能以函数方法等较小粒度进行组织。

假设我们有下边一个类,设计单元测试时,最好以各个功能函数为测试目标,而不是将类本身为测试目标:

//IPv4报文解析structIPv4Parser{IPv4Parser(constvoid*buffer,size_tsize);

size_tGetHeaderSize();//获取头部大小uint32_tGetSrcAddr();//获取源IPuint32_tGetDstAddr();//获取目的IP};

建议:为GetHeaderSize()GetSrcAddr()GetDstAddr()分别构造不同的测试输入数据。

不建议:为IPv4Parser类构造测试输入数据,然后对GetHeaderSize()GetSrcAddr()GetDstAddr()使用同样的数据进行单元测试。

常见的测试框架都支持通过测试套件(TestSuite)对测试用例(TestCase)在逻辑上进行组织,测试套件可以嵌套,整个单元测试可以组织为树状结构。

常见的测试框架还支持Fixture。Fixture是对测试环境进行组织,通过SetUp()TearDown()函数,以方便进行测试开始前的准备工作,以及测试完成后的清理工作。Fixture一般会与测试套件结合使用。

组织单元测试的几点准则:

用例设计

设计单元测试用例中有很多方法:等价类划分、边界值分析、路径测试……

在实践中,我们可以设计覆盖正常流程&异常流程两大类用例:

一个函数的内部实现可能是异常处理-正常流程-异常处理-正常流程的重复,比如这样:

size_tIPv4Parser::GetHeaderSize(){//异常处理if(buffer_size

//正常流程autoip=(constiphdr*)buffer;

//异常处理if(ip->version!=4)returnfalse;

//...}

因此我们在设计测试用例时,可以:

在设计测试用例过程中,可能会遇到被测函数需要与外部DB、文件、网络交互的情况,这时候需要使用Fakes/Stubs/Mocks进行模拟:

在实践中通常并不纠结这几个词语的区别,常被统称为插桩,对应的工具也一般被称作Mock工具。

GoogleTest是老牌测试框架,功能完善,用户很多。

Catch2是现代化测试框架,提供了很多特色功能,依赖简单,可以一试。

Boost.Test是Boost自带的测试框架,依赖Boost的程序可以直接使用,功能强大。

一些Mock工具编译参数选项Python单元测试

点击阅读。

单元测试必须经常跑

从增量到存量,从主要到次要

测试用例需要逐步积累

实践经验

思路:以黑盒指导功能验证,以白盒提升覆盖率

黑盒测试为主:

白盒测试为辅:

可能踩到的坑

不要被高覆盖率骗了

Debug/Release目标结果不一致

代码合并导致单测失败

提高代码的可测性

在编码过程中,多多考虑代码的可测性,可以让单元测试事半功倍:

THE END
1.python基础——如何测试代码?代码测试可通过的测试 你需要一段时间才能习惯创建测试用例的语法,但创建测试用例之后,再添加针对函数的单元测试就很简单了。 要为函数编写测试用例,可先导入模块unittest和要测试的函数,再创建一个继承unittest.TestCase的类,并编写一系列方法对函数行为的不同方面进行测试。 https://blog.csdn.net/weixin_49895216/article/details/134278533
2.白盒测试方法wx634e5f8a4276e的技术博客白盒测试又称透明盒测试、逻辑驱动测试,是测试被测单元内部如何工作的一种测试方法,根据程序内部逻辑结构及有关信息来设计和选择测试用例,对程序的逻辑结构进行测试,可覆盖全部代码、分支、条件和路径等。保证程序中所有关键路径的测试,防止由于没有执行的路径在实际投入运行后执行到发生意外的情况,衡量测试完整性,程序https://blog.51cto.com/u_15834920/5767451
3.安全测试方法目前主要安全测试方法有: ①静态的代码安全测试:主要通过对源代码进行安全扫描,根据程序中数据流、控制流、语义等信息与其特有软件安全规则库进行匹对,从中找出代码中潜在的安全漏洞。静态的源代码安全测试是非常有用的方法,它可以在编码阶段找出所有可能存在安全风险的代码,这样开发人员可以在早期解决潜在的安全问题。而https://xue.baidu.com/okam/pages/strategy/index?strategyId=141649071995450&source=natural
4.『软件测试4』耗子尾汁!2021年了,你还不知道这4种白盒测试方法吗?2、白盒测试的测试对象 白盒测试的测试对象是基于被测试程序的源代码,而不是软件的需求规格说明书。 使用白盒测试方法时,测试人员必须全面了解程序内部逻辑结构,检查程序的内部结构,从检查程序的逻辑着手,对相关的逻辑路径进行测试,最后得出测试结果。 3、白盒测试的原则 https://www.imooc.com/article/319673
5.一种基于uppaal模型的汽车软件源代码仿真测试方法1.一种基于UPPAAL模型的汽车软件源代码仿真测试方法,其特征是,实现步骤如下 (O根据需求规格说明,构建UPPAAL模型,构建好的UPPAAL模型中的全局声明部分定义的数据变量和管道变量就是测试系统中的输入变量和输出变量,利用构建好的UPPAAL模型进行仿真和功能需求验证; (2)根据UPPAAL模型的定义或者通过UPPAAL仿真器,确定测试系统https://www.xjishu.com/zhuanli/55/201210382231.html
6.单元测试常见问题及测试方法单元测试常用方法.png 驱动代码(Driver)指调用被测函数的代码:单元测试中,驱动模块通常包括调用被测函数前的数据准备、调用被测函数及验证相关结果三个步骤。 桩代码(Stub)是用来代替真实代码的临时代码。比如,某个函数A的内部实现中 调用了一个尚未实现的函数B,为了对函数A的逻辑进行测试,那么就需要模拟一个函数Bhttps://www.jianshu.com/p/2ed4a7c203e3
7.在intellijidea中快速生成测试代码腾讯云开发者社区在intellij idea中快速生成测试代码 将鼠标放到类的任意位置,摁下Ctrl+Shift+T,然后Create a new Test即可。 JUnit4为了保证每个测试方法都是单元测试,是独立的互不影响。所以每个测试方法执行前都会重新实例化测试类。 为什么Junit没有main()方法就能运行 https://cloud.tencent.com/developer/article/1884994
8.静态代码检测工具:原理作用优点及选择指南为了确保代码的质量和安全性,在应用开发阶段使用静态代码检测工具进行代码检测越来越重要。 一、静态代码检测工具的原理 静态代码检测工具直接对源代码进行分析和检查,而不需要实际运行代码,从而发现潜在的问题和缺陷。其主要原理包括以下几个方面: 1. 语法分析:静态检测工具通过解析源代码,进行语法分析,以检查代码中的https://zhuanlan.zhihu.com/p/642168605
9.TestStars星云测试精准测试ThreadingTest穿线测试选用某平台工具(简称TT)对WIFI小车机器人的Android手机控制平台进行黑盒手工的测试,达到小车控制与代码逻辑分析。 三、静态测试与代码审查 静态测试内容里面要求采取多种的测试方法,例如‘低复杂度的强制要求’一般需要通过满足一定的度量指标来实现,度量指标包括圈复杂度、嵌套深度等等,除此之外 静态测试还要求一些其它的http://www.threadingtest.com/newstest/%E7%B2%BE%E5%87%86%E6%B5%8B%E8%AF%95%E5%9C%A8%E6%99%BA%E8%83%BD%E6%9C%BA%E5%99%A8%E4%BA%BA%E4%B8%8A%E7%9A%84%E5%BA%94%E7%94%A8.html
10.tiantian010日志测试生涯@flaky(max_runs=3, min_passes=2):第一次执行失败了,将会再次重复执行它3次,如果这3次中有2次成功了,则认为这个测试通过了。 查看(714)评论(0)收藏分享管理 Robot Framework类似功能 2019-09-10 18:02:34 查看(745)评论(0)收藏分享管理 不懂代码也能测试的接口自动化平台 http://www.51testing.com/index.php?uid-154419-action-spacelist-type-blog-itemtypeid-4546
11.面经2022年软件测试面试题大全(持续更新)附答案可测试性:每项需求都能够通过设计测试用例或其他的验证方法来进行测试; 可修改性:每项需求只应在SRS中出现一次,这样更改会容易保持一致性; 可跟踪性:在每项软件需求与它的根源与设计元素,源代码,测试用例之间建立起链接,而这种可跟踪性要求每项需求都必须以一种结构化的,粒度好的方式编写并单独标明,而不是大段https://maimai.cn/article/detail?fid=1730797197&efid=rTTgV-zsthsezl4x1LC2pw
12.《软件测验基本》期末考卷及参考谜底结构测试,静态测试,动态测试 10、软件是包括___﹑___﹑___的完整集合。 程序,数据,相关文档 11、边界值分析法属于___。 黑盒测试 12、单元测试是以___说明书为指导,测试源程序代码。 详细设计 13、集成测试以___说明书指导,测试软件结构。 概要设计 14、https://www.360docs.net/doc/ada5fd4e31687e21af45b307e87101f69e31fba2.html
13.使用测试资源管理器运行单元测试测试资源管理器还可以运行第三方和开放源代码单元测试框架,它们实现了测试资源管理器外接程序接口。 你可以通过 Visual Studio Extension Manager 和 Visual Studio 库添加其中许多框架。 有关详细信息,请参阅安装第三方单元测试框架。 你可以从代码快速生成测试项目和测试方法,或者根据你的需要手动创建测试。 当使用 Inthttps://docs.microsoft.com/zh-cn/visualstudio/test/unit-test-basics
14.医疗软件产品技术审评规范(2017版)确认是指通过提供客观证据认定软件满足用户需求和预期用途,通常是指在真实或模拟使用环境进行的用户测试。可追溯性分析是指追踪需求规范、设计规范、源代码、测试、风险管理之间的关系,分析已识别关系的正确性、一致性、完整性和准确性。 A级提供系统测试、用户测试的计划和报告摘要,描述测试的条件、工具、方法、通过准则https://yjj.beijing.gov.cn/yjj/ztzl48/ylqxjgfwzn/jsscgfzl64/yycp60/11001660/
15.科学网—欠小伙伴一个火山图,这次给你们补上扫描关注微信公众号,后台回复火山图,获取源代码和测试数据 # 读入数据 df = readFlie('./DEGs_result.txt',type = 'txt') # 绘图 fg=wn_volcano(Symbol = rownames(df),logFC=df$logFC,Pvalue=df$FDR) # 展示图片 fg # 保存图片 savePlots(path = './fg.pdf',plot = fg,type = 'pdf',widthhttps://wap.sciencenet.cn/home.php?mod=space&do=blog&id=1251072
16.代码测试工具SourceMonitorv3.5.8绿色版下载代码测试工具 SourceMonitor v3.5.8绿色版主要功能: 通过源文件快速收集,单次指标。 检测C++,C,C#,VB.NET,Java和Delphi源代码,Visual Basic(VB6)或HTML。 包括方法和功能水平指标C ++,C,C#,VB.NET,Java和Delphi的。 未经修改的复杂性度量的选择。 http://www.winwin7.com/soft/xtgj-2880.html