如何开发一个白盒测试工具(基于Python语言)

☆樱花仙子☆ 提交于 2019-12-08 10:59:41

问题背景

网上有许多介绍如何使用一种白盒测试工具的教程,但是很少有教人自己开发一个白盒测试工具的教程。因此我做了一次这样的实验,根据Python语言的特性,开发一个Python代码的白盒测试工具,实现以下功能:记录程序执行路径、获取函数调用关系、计算代码覆盖率、语句覆盖率、分支覆盖率(判定覆盖率)、字节码覆盖率。其中字节码覆盖率是我自己造的一个词,本想用于实现计算条件覆盖率的,奈何没有成功。

实验环境

编程语言:Python
编程环境:Linux16.04 + Anaconda2 + Python2.7
完整代码可以到我的GitHub上查看:https://github.com/Jancy1072/pycoverage

原理

1、Python是基于虚拟机的语言,首先是通过编译器编译成字节码文件,然后在运行时通过解释器解释成机器文件。Python是一种先编译后解释的语言。

我们在硬盘上看到的.pyc文件就是字节码文件,它来源于:当python程序运行时,编译的结果保存在位于内存中的PyCodeObject中,当Python程序运行结束时,Python解释器则将PyCodeObject写回到.pyc文件中。当python程序第二次运行时,程序首先会在硬盘中寻找.pyc文件,如果找到,则直接载入,否则就重复上面的过程。在载入之前还会先检查一下.py文件和.pyc文件保存的最后修改日期,如果不一致则重新生成一份.pyc文件。所以.pyc文件其实是PyCodeObject的一种持久化保存方式。把.py文件编译成.pyc文件,最大的优点在于运行程序时不需要重新对该模块进行解释。

因此,利用.pyc文件,我们可以获得许多程序运行的信息。

2、.pyc文件解析
(1).pyc文件一般由3个部分组成:

  • 最开始4个字节是Maigc int,标识此.pyc的版本信息,不同版本的Magic都在Python/import.c内定义。
  • 接下来四个字节是.pyc产生的时间(TIMESTAMP,1970.01.01到产生.pyc时候的秒数),int型。
  • 接下来是序列化了的PyCodeObject(此结构在Include/code.h内定义),序列化方法在Python/marshal.c内定义。

(2)PyCodeObject的类型定义:
在这里插入图片描述(3)需要注意的几个关键点:

  • 在Python代码中,每个作用域对应一个PyCodeObject对象,所以会出现嵌套,需要递归处理。
  • 行号对照表co_lnotab可以看做是顺次串成的的字节数组(字符串),它是字节码在 co_opcode中的index增量,对应的源码的行号增量。
  • co_code是Python opcode组成的序列,具体有哪些opcode可以参看Include/opcode.h,有些opcode有参数,有些没有参数:大于等于90的opcode有参数,参数占2个字节。

3、Python钩子函数
(1)sys.settrace(my_trace_function)是Python的系统模块提供的挂载函数。我们可以自定义代码追踪的钩子函数,挂载到虚拟机上,当程序运行时,钩子函数能够监听触发的事件并处理。执行每一行代码,都会触发事件,调用钩子函数。

(2)my_trace_function(frame, event, arg)是我们自定义的钩子函数。能够监听call、line、return、exception、opcode事件。而frame参数有f_code、f_lineno、f_back 等属性,我们可以获取程序执行时虚拟机的内部数据。

实现

1、实现钩子函数
在这里插入图片描述

  • 记录程序执行路径:line事件触发时,保存当前行号和对应的代码语句。
  • 函数调用
    call事件触发时,保存被调用的函数名、调用者。
    line事件触发时,词法分析,保存定义的函数名。
  • 语句覆盖
    line 事件触发时,保存当前行号和对应的代码语句。
  • 分支覆盖
    line 事件触发时,词法分析,记录前一个语句和当前的语句,构成(pre, current)这样的行号对。

2、解析.pyc文件
在这里插入图片描述

  • 读取.pyc文件,得到PyCodeObject对象。
  • 递归统计字节码的信息。
  • 利用行号表,得到总行数、行号。
  • 词法分析得到总的分支数。

3、字节码覆盖
在这里插入图片描述
(1)利用co_code属性统计程序中所有字节码的信息。
(2)行号表记录的是每一行的字节码的偏移量。每执行一行代码,都会触发一次line事件。
(3)修改行号表,重新构建一个.pyc文件,使得每执行一个字节码触发一次 line事件。
(4)自定义钩子函数,在line事件触发时统计实际执行的字节码。
(5)实现字节码覆盖是实现条件覆盖的基础。

实验结果

1、本工具包含trace.pygeneral_analyse.pybytes_analyse.pyopcode.py四个源程序文件。
(1) trace.py中定义了钩子函数,能够记录程序运行执行路径,保存了可以用于计算语句覆盖和分支覆盖的中间结果。
(2) general_analyse.py中检查了函数调用关系,计算了语句覆盖率、分支覆盖率。
(3) bytes_analyse.py中统计了可用字节码的总数和实际执行的字节码数量,统计未执行的字节码,计算字节码覆盖率。并且综合所有类型的覆盖率情况,计算总体覆盖率。
(4) opcode.py中定义了操作码和操作名的映射关系。

2、待测试代码文件case1.py
在这里插入图片描述
测试代码中包含简单的函数调用关系、顺序结构、条件结构、循环结构。

3、运行trace.py,输入参数case1.py
在这里插入图片描述
得到程序执行路径,结果保存在trace_result.txt文件中。生成coverage.txt文件,保存函数调用关系、语句执行情况、分支情况。

4、运行general_analyse.py程序。
在这里插入图片描述
得到函数调用关系(树形)、语句覆盖率、分支覆盖率。结果保存在analyse_result.txt文件中。

5、运行bytes_analyse.py程序。
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

得到字节码覆盖率、未执行的字节码。总体覆盖率保存在analyse_result.txt文件中。

总结

1、优点
深入理解了Python语言的编译解释机制和语言特性,实现了白盒测试工具的基础功能,能够分析程序常见结构流如顺序、选择与循环,记录程序执行路径,检测函数调用关系,计算语句覆盖率、分支覆盖率。而且从更小的粒度来计算覆盖率,实现了字节码覆盖,这是计算条件覆盖率的基础。

2、缺点
在计算字节码覆盖率时修改了.pyc文件,使得原有的行号等信息丢失。未能实现条件覆盖率的计算。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!