“有迹可循”的灰盒测试分析
前言
当一笔代码改动被提交,我们的测试对象即是改动点及其影响范围。那么如何准确、高效地确认影响范围呢?
若能够搭建一个具备“输入被改动函数”—“输出函数代码层面相关信息”功能的测试框架,用以辅助灰盒测试分析,想必可以事半功倍。下面小编便介绍一下自己的实现思路与流程。
静态代码扫描
欲实现变更函数的信息输出,首先要获取项目代码中各函数的特征信息——小编在这一环节采取了使用软件“understand”进行静态代码扫描的方案,以期得到每个函数对应的调用/被调用关系、实体结构、控制流等方面的信息。
软件的扫描流程比较简单,不再于此赘述,下面通过代码说明如何使用其Python相关API对扫描结果数据进行处理——将函数相关信息绘制成图像以供直观查阅:
def drawGraghofFunc(curPath, db, graghCategoryList):
# 遍历数据库形式的代码扫描结果中的所有函数
for func in db.ents("function, method, procedure"):
# 创建以各函数名称命名的文件夹用以存储图像
funcName = func.name()
# 有效函数名称及去重判断
if "." in funcName and not funcName.startswith("("):
if os.path.exists(os.path.join(curPath, funcName)):
time.sleep(1)
continue
graghPath_dir = createGraghDir(funcName, curPath)
gevent_list = []
# 由于数据量较多对不同类别图像绘制采用协程处理
graghCategoryList = ["callby_", "call_", "controlFlow_", "declaration_", "clusterButterfly_"]
for graghCategory in graghCategoryList:
gevent_list.append(gevent.spawn(drawBaseOnGraghCategory, func, funcName, graghCategory, graghPath_dir))
gevent.joinall(gevent_list)
def drawBaseOnGraghCategory(func, funcName, graghcategory, graghPath_dir):
graghName = graghcategory + funcName + ".png"
graghName_dir = os.path.join(graghPath_dir, graghName)
# 调用underder提供的draw方法进行图像绘制并保存
# 调用关系、被调用关系、控制流、函数实体、结构等
if graghcategory == "callby_":
func.draw("Called By", graghName_dir)
elif graghcategory == "call_":
func.draw("Calls", graghName_dir)
elif graghcategory == "controlFlow_":
func.draw("Control Flow", graghName_dir)
elif graghcategory == "declaration_":
func.draw("Declaration", graghName_dir)
elif graghcategory == "clusterButterfly_":
func.draw("Cluster Call Butterfly", graghName_dir)
为使函数信息图像数据更便于检索,且易于后续拓展,小编采取了在MongoDB中以类名创建collection,并于collection下存储所属此类的全部函数及其相关图像信息的方案。代码如下:
def saveGraphPathToMongo(graghDataPath):
# 遍历存储图片的文件夹
for func_cls_name in os.listdir(graghDataPath):
if os.path.isdir(os.path.join(graghDataPath, func_cls_name)):
saveDatabyFuncName(func_cls_name)
def saveDatabyFuncName(func_cls_name):
# 得到类名、函数名
func_cls = func_cls_name.split(".")[0]
func_name = func_cls_name.split(".")[-1]
# 创建collection及相应存储单元Document
scan_col = scan_db[func_cls]
func_id = {"_id": func_name}
func_info = {"_id":func_name, "call":"", "callby":"", "clusterButterfly":"", "controlFlow":"", "declaration":""}
# 根据图像名称编辑待存储数据
for func_cls_name_gragh in os.listdir(os.path.join(graghDataPath, func_cls_name)):
if "call_" in func_cls_name_gragh:
func_info["call"] = func_cls_name_gragh
elif "callby_" in func_cls_name_gragh:
func_info["callby"] = func_cls_name_gragh
elif "clusterButterfly_" in func_cls_name_gragh:
func_info["clusterButterfly"] = func_cls_name_gragh
elif "controlFlow_" in func_cls_name_gragh:
func_info["controlFlow"] = func_cls_name_gragh
elif "declaration_" in func_cls_name_gragh:
func_info["declaration"] = func_cls_name_gragh
# 向数据库中插入数据
if not scan_col.find_one(func_id):
scan_col.insert_one(func_info)
考虑到在针对函数的调用/被调用关系链路进行展现时,若仅输出图像,则不便于根据链路上的其它函数名称进行延伸检索。由此,将代码扫描结果中调用/被调用函数名称同样予以保存,以便输出。代码如下:
def saveCallAndCalledToMongo(db):
for ent in db.ents("function, method, procedure"):
funcName = ent.name()
if not "." in funcName:
continue
if funcName.startswith("("):
continue
# 获取类名、函数名
func_cls = funcName.split(".")[0]
func_name = funcName.split(".")[1]
scan_col = scan_db[func_cls]
func_id = {"_id": func_name}
call_funcName = []
called_funcName = []
# 找对函数对应的MongoDB中Document
func_info = scan_col.find_one(func_id)
if func_info:
# 调用understand中PythonAPI相关方法
# 将调用/被调用函数名称予以存储入库
for ref in sorted(ent.refs("call","",True),key= lambda ref: ref.ent().name()):
callee = ref.ent().name()
call_funcName.append(callee)
scan_col.update_one(func_id, {'$set':{'call_funcName':call_funcName}})
for ref in sorted(ent.refs("callby","",True),key= lambda ref: ref.ent().name()):
called = ref.ent().name()
called_funcName.append(called)
scan_col.update_one(func_id, {'$set':{'called_funcName':called_funcName}})
有了数据库作为基础,考虑到易用性与兼容性,小编采用了WEB平台作为函数信息展现的形式。
如图,通过输入框提交基于当前git log信息的git diff相关命令时,则获取diff数据中代码发生变更的函数名称(利用数据格式进行判断,如被修改方法前的“@@”),并予以信息展现。
而直接输入函数名称进行search,则在测试分析过程中提供了更大的灵活性。
结语
基于这样的信息呈现,灰盒测试分析是不是更加有迹可循了呢? 进一步来说,若能建立起各函数与测试点或用例间的联系,它在测试方向与细节上提供的帮助将会更为可观——这同样也是小编的后续目标,欢迎各位小伙伴交流学习。
- Codeforces 834D The Bakery【dp+线段树维护+lazy】
- memcached安装及.NET中的Memcached.ClientLibrary使用详解
- AtCoder Beginner Contest 069【A,水,B,水,C,数学,D,暴力】
- 2017"百度之星"程序设计大赛 - 资格赛【1001 Floyd求最小环 1002 歪解(并查集),1003 完全背包 1004 01背包 1005 打表找规律+卡特兰数】
- 洛谷 2634&&BZOJ 2152: 聪聪可可【点分治学习+超详细注释】
- 【经验总结】Java在ACM算法竞赛编程中易错点
- 【Java学习笔记之六】java三种循环(for,while,do......while)的使用方法及区别
- 类A是公共的,应在名为A.java的文件中声明错误
- 逆天通用水印支持Winform,WPF,Web,WP,Win10。支持位置选择(9个位置 ==》[X])
- 【Java学习笔记之七】java函数的语法规则总结
- BZOJ 3038: 上帝造题的七分钟2【线段树区间开方问题】
- BZOJ 3211: 花神游历各国【线段树区间开方问题】
- WP、Win10开发或者WPF开发时绘制自定义窗体~例如:一个手机
- 【Java学习笔记之八】JavaBean中布尔类型使用注意事项
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法