使用Coverage分析WSGI项目的代码覆盖率

关于Coverage

Coverage是Python代码覆盖率分析工具,有关它的介绍和安装方法请见:
Python代码覆盖率分析工具Coverage

用Python启动的web服务可以方便地使用Coverage分析其覆盖率,具体请见:
使用Coverage分析Python web项目的代码覆盖率

下面来说说WSGI项目的分析,这方面的资料较少,需要一定摸索。

使用Coverage分析WSGI项目的代码覆盖率

一个uWSGI + Django的项目,它的启停命令如下:

sudo uwsgi --ini /xxx/uwsgi.cfg
sudo pkill -9 -f /xxx/uwsgi.cfg

所以,你是没有办法像使用Coverage分析Python web项目的代码覆盖率一样用Coverage命令行coverage run的形式启动它的。必须使用Coverage api。

关于Coverage api,参见文档:
http://coverage.readthedocs.org/en/latest/api_coverage.html

还需要用到一点.coveragerc配置,参见文档:
http://coverage.readthedocs.org/en/latest/config.html

修改wsgi.py文件

对于WSGI项目,需要修改创建WSGI application的文件,加入coverage api代码。
本来它的代码是这样的:

import os

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xxx.settings")

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

需要在其前后加入coverage控制,以下代码须安装Coverage 4.0

###########
import coverage, atexit
cov = coverage.Coverage(branch=True, concurrency="gevent", config_file=".coveragerc")
cov.start()
###########
import os

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xxx.settings")

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
###########
def save_coverage():
    cov.stop()
    cov.save()
atexit.register(save_coverage)
###########

解释一下这一句:

cov = coverage.Coverage(branch=True, concurrency="gevent", config_file=".coveragerc")
  • branch是指要统计分支代码覆盖率,加上这个参数可使统计更精确,具体区别参见文档:http://coverage.readthedocs.org/en/latest/branch.html

  • concurrency指被测代码使用的concurrency library,选项有greenlet, eventlet, gevent, thread(默认)。本项目使用了gevent,这里设置成gevent。

  • config_file指Coverage的配置文件,这个配置文件主要用于指定parallel这个参数。由于Coverage构造函数不支持parallel参数,必须使用配置文件。

使用.coveragerc文件作为Coverage的配置文件

一般的Web项目都是多进程,这需要Coverage分析子进程的覆盖率,需要用到Coverage配置文件。
官方文档参见:http://coverage.readthedocs.org/en/latest/config.html

在这个项目中,.coveragerc内容如下,它需要与coverage api所在路径一致,即与wsgi.py同目录:

[run]
branch = True
parallel = True

这个配置使Coverage监测被测代码子进程的覆盖率,如果被测代码是多进程的,必须使用此参数。

在Coverage命令行启动中,可以这样指定:

coverage run --parallel-mode xxx.py

但在api方式中,只能使用config_file设置。

关于Coverage构造函数config_file参数,文档说不设置默认不使用配置文件,实际不是,只要有.coveragerc文件,就会使用其中的配置。

atexit.register方法

关于wsgi项目的覆盖率统计,最初我看到的资料(也是唯一的)是:
http://stackoverflow.com/questions/19025336/how-to-get-coverage-data-from-a-django-app-when-running-in-gunicorn\\

里面提到需要这样保存覆盖率结果:

def save_coverage():
    cov.stop()
    cov.save()
atexit.register(save_coverage)

意思是使用atexit.register注册回调函数,以在程序退出时保存结果。但为了触发atexit.register,需要对被测进程执行kill -HUP。

经过实测,有的项目是不需要执行kill -HUP的。子进程在收到请求时会自动退出,保存覆盖率结果,同时主进程会重启一个子进程。

这就意味着加入Coverage api以后,服务收到的每个请求都会重启一个子进程!这会严重影响性能。所以这种覆盖率统计只能在线下做。

Coverage结果收集

经过如上修改后,正常用uWSGI启动服务:

sudo uwsgi --ini /xxx/uwsgi.cfg

启动后,执行测试case,可以见到wsgi.py所在目录下出现多个.coverage开头的文件,文件名格式为.coverage.<机器名>.<进程号>.<随机数>。

xxx@xxx:/xxx$ ll
total 2708
drwxr-xr-x 11 root root   4096 Sep 25 11:46 ./
drwxr-xr-x  6 root root   4096 Sep 25 08:30 ../
-rw-rw-rw-  1 root root 284691 Sep 25 11:46 .coverage.xxx.15845.747211
-rw-rw-rw-  1 root root 284917 Sep 25 11:45 .coverage.xxx.15846.592706
-rw-rw-rw-  1 root root 284274 Sep 25 11:45 .coverage.xxx.15847.688607
-rw-rw-rw-  1 root root 284583 Sep 25 11:45 .coverage.xxx.15858.136003
-rw-rw-rw-  1 root root 284274 Sep 25 11:46 .coverage.xxx.15867.746159
-rw-rw-rw-  1 root root 284691 Sep 25 11:46 .coverage.xxx.15876.004083
-rw-rw-rw-  1 root root 283820 Sep 25 11:46 .coverage.xxx.15886.921243

有7个文件,意味着发送了7个请求。

测试结束后,需要合并测试结果,生成报告:

coverage combine
coverage report -m yyy/*
coverage html yyy/*
coverage xml yyy/*
coverage erase
  • combine会合并7个文件成1个.coverage,因为最后Coverage统计的是.coverage的结果。

  • report/html/xml:直接在终端显示报告/生成html报告/生成xml报告,后面加路径可以限制显示哪些代码的覆盖率。(这里文档说在Coverage构造函数里使用include参数可以限制,实测,没有用…)

  • erase会删除.coverage文件,保证不会影响下次统计的结果。

生成的报告非常清晰,html和xml可以直接点击进入代码文件查看。

  • coverage report结果:

  Name                         Stmts   Miss Branch BrPart  Cover   Missing
  ------------------------------------------------------------------------
  yyy/__init__.py           0      0      0      0   100%   
  yyy/111.py      89     12     16      3    86%   82, 89-91, 104, 108-110, 123, 127-129, 81->82, 103->104, 122->123
  yyy/222.py           60     44     14      0    22%   30-89, 97-103
  yyy/333.py        268     31     74     16    85%   48, 56-57, 70, 78-79, 92, 109, 117, 131, 154, 175, 195-206, 217, 235, 256, 277, 304, 327, 344, 366-367, 45->48, 67->70, 89->92, 106->109, 116->117, 128->131, 151->154, 172->175, 184->181, 214->217, 232->235, 253->256, 274->277, 301->304, 324->327, 341->344
  ……(略)
  ------------------------------------------------------------------------
  TOTAL                         7180   1872   1976    414    70%   
  • XML(集成到Jenkins):
    使用Coverage分析WSGI项目的代码覆盖率_第1张图片

  • XML(集成到Sonar):
    使用Coverage分析WSGI项目的代码覆盖率_第2张图片

  • HTML:
    使用Coverage分析WSGI项目的代码覆盖率_第3张图片

你可能感兴趣的:(django,uwsgi,wsgi,python,coverage)