“有迹可循”的灰盒测试分析

2020-08-30 14:42:20 浏览数 (2)

前言

当一笔代码改动被提交,我们的测试对象即是改动点及其影响范围。那么如何准确、高效地确认影响范围呢?

若能够搭建一个具备“输入被改动函数”—“输出函数代码层面相关信息”功能的测试框架,用以辅助灰盒测试分析,想必可以事半功倍。下面小编便介绍一下自己的实现思路与流程。

静态代码扫描

欲实现变更函数的信息输出,首先要获取项目代码中各函数的特征信息——小编在这一环节采取了使用软件“understand”进行静态代码扫描的方案,以期得到每个函数对应的调用/被调用关系、实体结构、控制流等方面的信息。

软件的扫描流程比较简单,不再于此赘述,下面通过代码说明如何使用其Python相关API对扫描结果数据进行处理——将函数相关信息绘制成图像以供直观查阅:

代码语言:javascript复制
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下存储所属此类的全部函数及其相关图像信息的方案。代码如下:

代码语言:javascript复制
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)

考虑到在针对函数的调用/被调用关系链路进行展现时,若仅输出图像,则不便于根据链路上的其它函数名称进行延伸检索。由此,将代码扫描结果中调用/被调用函数名称同样予以保存,以便输出。代码如下:

代码语言:javascript复制
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,则在测试分析过程中提供了更大的灵活性。

结语

基于这样的信息呈现,灰盒测试分析是不是更加有迹可循了呢? 进一步来说,若能建立起各函数与测试点或用例间的联系,它在测试方向与细节上提供的帮助将会更为可观——这同样也是小编的后续目标,欢迎各位小伙伴交流学习。

0 人点赞