django工程黑盒测试使用coverage.py进行覆盖率统计

来源:互联网 发布:e网络卡盟 编辑:程序博客网 时间:2024/06/05 14:29

        公司有个用django框架制作的server,有一天领导跟我说,虽然我们做黑盒测试,但是我希望能统计代码覆盖度,这样能够完善我们的测试用例。于是就把这个任务交给我了。刚接到手我觉得这事很easy。django是python写的,python代码覆盖工具应该是很好找的,就百度了一下马上搜到了coverage.py这个东西。当然在实际使用中遇到了很多问题,折腾了好几天才完成。

        coverage.py的官网http://nedbatchelder.com/code/coverage/  我下载了最新的3.6版本,然后测试了一下,发现是可用的,且使用较为方便。

        按照官网或者网络搜索的说法,一般coverage有3种用法

        第一种使用命令行执行python脚本。比如有个脚本aaa.py,通常执行脚本方式为python aaa.py arg1 arg2,那么你只需使用coverage run aaa.py arg1 arg2执行完成后就能看到在当前目录下有个隐藏的.coverage文件,然后使用命令coverage report就能打印出来统计结果,想作成报表就输入coverage html就会有个文件夹生成然后打开里面的index.html就看到报表了,想要加入分支统计可以coverage run --branch aaa.py arg1 arg2。

        第二种是在代码中插入API

import coveragecov = coverage.coverage()cov.start()# .. call your code ..cov.stop()cov.save()cov.html_report(directory="abc")
        这种方式只需按照普通方式运行python脚本,执行完了就有哪个结果文件,如果加了最后那句html_report直接会生成abc这样一个文件夹就有报表了

        第三种方法就很暴力了,考虑到有些python程序不是import一个包然后调用里面的类或者方法,而是直接使用后台执行这个脚本,比如使用popen,那在脚本中执行的这个脚本就没统计到覆盖率了,于是这种用法就是解决这个问题。在环境变量里面加入COVERAGE_PROCESS_START=true,然后在sitecustomize.py这个文件最后加入这样一段

import coveragecoverage.process_startup()
         这个sitecustomize.py可以搜索下,我的在/usr/lib/python2.7下。这种用法原理就是,当python启动时都会执行sitecustomize.py,所以不管是在终端里面使用python,或者在python里面再在后台执行python,都会执行sitecustomize.py,就都会启动coverage了

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

         接下来我就描述下django程序在使用coverage中遇到的问题。django是一个web框架,如果需要部署到线上,那么就挂在apache下,或者nginx下等等。所以它并不是一个独立的,你可以通过python xxx.py这种方式可以运行的程序。所以第一种用法就不行了。那么就考虑第二种或者第三种。

        先说第三种吧。理论上重启apache或者stop apache终止了程序就会有一个.coverage生成,但是找不到。看了下代码coverage.process_startup()里面的代码实际是这样的

def process_startup():    cps = os.environ.get("COVERAGE_PROCESS_START")    if cps:        cov = coverage(config_file=cps, auto_data=True)        cov.start()        cov._warn_no_data = False        cov._warn_unimported_source = False
        调试过程中发现cps是None,我命名设置了这个环境变量,我查看了我的apache配置,运行用户确实是我当前用户,使用getpass.getuser()返回的用户名也没错,但是为什么os.environ返回中没有这个环境变量。后来查找资料知道,apache里面获取的环境变量是apache外部传进去的,并非当前用户的环境变量,所以代码里获得环境变量都是一些apache的配置。那么我强制将cps改成true,重新运行,发现还是没有生成.coverage,这个问题困扰了我很久,后来是在尝试第二种方法的时候发现它把.coverage文件写道root的根目录"/"下,但是没有权限写不进去,于是尝试把“/”进行chown改成apache运行的那个用户,发现可以了。但是每调一个接口就把前面的结果覆盖掉了,说明这种apache下的模块,不是持续运行的,而是发起一个http请求就回去调一个python代码,每调一次就去start一次就覆盖调了,并且将根目录的用户名改成非root显然不是长久之计

        再说第二种办法,通过api的形式。要么让开发在每个接口调用前后插入api来统计,这显然也是不合理。研究了下django发现所有接口的调用都是通过一个回调函数实现的,该回调的文件在/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py里面又这样一段代码

if response is None:    try:        response = callback(request, *callback_args, **callback_kwargs)    except Exception as e:

只要在callback前后插入api就可以实现所有接口调用都统计了于是做了如下修改

try:    from coverage import coverage    cov = coverage(branch = True)    cov.start()    response = callback(request, *callback_args, **callback_kwargs)    cov.stop()    cov.save()except Exception as e:

同样会遇到之前碰到两个问题,一个是它要把结果写道“/”目录下。没权限,每调一个接口就会调用一次start导致覆盖了之前了结果,如果像start一次之后不再start,我觉得唯一的办法就是export一个环境变量作为标示,靠外部来调的程序我像可能没办法使用一个全局变量来改变其值作为标示。但是环境变量刚也说了不行,就选这次增加一个环境变量下次进来还是找不到那个变量。突然想起coverage有个combine功能,可以将多个结果文件进行合并,那就每次重新生成一个,最后合并起来就可以了。于是


try:    from coverage import coverage    import os    if os.path.exists("/.coverage"):        import time        os.rename("/.coverage", "/.coverage." + str(int(time.time())))    cov = coverage(branch = True)    cov.start()    response = callback(request, *callback_args, **callback_kwargs)    cov.stop()    cov.save()except Exception as e:

这个方法成了,调用10个接口就会生成10个.coverage开头的文件,然后去"/"目录下执行下coverage combine就合并,那么就剩最后一个问题就是想办法将结果文件写到别的目录下去,看了下coverage这个包的构造函数并没有提供设置路径这个参数,那就自己加吧,要改动如下几个地方

/usr/local/lib/python2.7/dist-packages/coverage-3.6-py2.7-linux-i686.egg/coverage/control.py

class coverage(object):    """Programmatic access to coverage.py.    To use::        from coverage import coverage        cov = coverage()        cov.start()        #.. call your code ..        cov.stop()        cov.html_report(directory='covhtml')    """    def __init__(self, directory=None, data_file=None, data_suffix=None, cover_pylib=None,                auto_data=False, timid=None, branch=None, config_file=True,                source=None, omit=None, include=None):

加入directory=None

然后同个文件148行修改


self.data = CoverageData(            directory = directory, basename=self.config.data_file,            collector="coverage v%s" % __version__            )

加入directory = directory最后修改下CoverageData类,这个类在/usr/local/lib/python2.7/dist-packages/coverage-3.6-py2.7-linux-i686.egg/coverage/data.py,如下

class CoverageData(object):    def __init__(self, directory=None, basename=None, collector=None):        """Create a CoverageData.        `basename` is the name of the file to use for storing data.        `collector` is a string describing the coverage measurement software.        """        self.collector = collector or 'unknown'        self.use_file = True        # Construct the filename that will be used for data file storage, if we        # ever do any file storage.        self.filename = basename or ".coverage"        if directory:            self.filename = directory + "/" + self.filename        else:            self.filename = os.path.abspath(self.filename)

这样改动别的程序不指定路径也不会影响以前的功能,最后再修改django的代码如下

if response is None:    try:        from coverage import coverage        import os        if os.path.exists("/tmp/.coverage"):            import time            os.rename("/tmp/.coverage", "/tmp/.coverage." + str(int(time.time())))        cov = coverage(directory = "/tmp", branch = True)        cov.start()        response = callback(request, *callback_args, **callback_kwargs)        cov.stop()        cov.save()    except Exception as e:


大功告成了,这样就会把结果文件全部写到tmp下,然后测试完成去合并下结果文件即可
那么其他的web框架比如web.py我相信也可以用同样的方式去做


	
				
		
原创粉丝点击