上一篇文章《用pytest-cov获取flask项目的测试代码覆盖率》展示了用pytest的测试用例验证flask的函数,获取代码覆盖率信息。但是上述方法要求web服务没有提前启动,而是由pytest来启动,然后运行测试用例。
那么对于已经启动的web服务,能否也用pytest来做代码覆盖率的验证呢?答案是肯定的,需要使用Coverage.py。
整体思路
- 手动启动Flask Web项目,项目启动代码中包含coverage信息收集
- pytest的testcase使用requests模块对Web服务发起HTTP请求,对回复进行验证
- 手动退出Web项目,终止coverage信息收集,输出报告
源码展示
- 文件目录结构
admin@pc:~/my_flask_app$ tree -a
.
├── app.py
├── __init__.py
└── tests
└── test_app.py
1 directory, 3 files
- app.py主程序中,需要加入
- 通过环境变量判断是否启动COV收集
- 定义函数响应"Ctrl+C",从而停止COV收集,输出COV报告
admin@pc:~/my_flask_app$ cat app.py
import os
import signal
import sys
from flask import Flask, jsonify
# 仅在测试模式下启用覆盖率
if os.getenv("FLASK_COVERAGE"):
import coverage
COV = coverage.coverage(branch=True, include="./app.py") # 确保只覆盖项目代码
COV.start()
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
@app.route('/api/data')
def get_data():
return jsonify({'data': 'Here is some data'})
def handle_sigint(signum, frame):
"""Handle Ctrl+C (SIGINT) to properly save coverage data."""
if os.getenv("FLASK_COVERAGE"):
COV.stop()
COV.save()
print("\nCoverage Report:")
COV.report()
COV.html_report(directory='htmlcov')
COV.erase()
sys.exit(0)
if __name__ == '__main__':
# 捕获 SIGINT 信号 (Ctrl+C)
signal.signal(signal.SIGINT, handle_sigint)
# 启动 Flask 应用
app.run()
admin@pc:~/my_flask_app$
- 测试用例中要用到requests,需要保证已经安装
admin@pc:~/my_flask_app$ cat tests/test_app.py
# tests/test_app.py
import requests
def test_hello_world():
"""Test the root route"""
response = requests.get('http://127.0.0.1:5000/')
assert response.status_code == 200
assert response.text == 'Hello, World!'
def test_get_data():
"""Test the /api/data route"""
response = requests.get('http://127.0.0.1:5000/api/data')
assert response.status_code == 200
assert response.json() == {'data': 'Here is some data'}
admin@pc:~/my_flask_app$
运行和调试
- 首先设置环境变量然后运行app
admin@pc:~/my_flask_app$ export FLASK_APP=my_flask_app.app
admin@pc:~/my_flask_app$ export FLASK_COVERAGE=1
admin@pc:~/my_flask_app$
admin@pc:~/my_flask_app$ python3 app.py
* Serving Flask app 'app'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
- 另外开一个端口,运行测试用例,都是pass的
admin@pc:~/my_flask_app$ pytest tests/
================================================================= test session starts ==================================================================
platform linux -- Python 3.10.12, pytest-8.3.3, pluggy-1.5.0
rootdir: /home/admin/my_flask_app
plugins: anyio-4.4.0, cov-5.0.0
collected 2 items
tests/test_app.py .. [100%]
=================================================================== warnings summary ===================================================================
../../../usr/lib/python3/dist-packages/requests/__init__.py:87
/usr/lib/python3/dist-packages/requests/__init__.py:87: RequestsDependencyWarning: urllib3 (2.2.2) or chardet (4.0.0) doesn't match a supported version!
warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported "
tests/test_app.py::test_hello_world
tests/test_app.py::test_get_data
/home/admin/.local/lib/python3.10/site-packages/urllib3/poolmanager.py:315: DeprecationWarning: The 'strict' parameter is no longer needed on Python 3+. This will raise an error in urllib3 v2.1.0.
warnings.warn(
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
============================================================ 2 passed, 3 warnings in 0.19s =============================================================
admin@pc:~/my_flask_app$
- 回到第一个终端,用ctrl+c中断app的运行,可以看到打印
admin@pc:~/my_flask_app$ python3 app.py
* Serving Flask app 'app'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
127.0.0.1 - - [29/Sep/2024 10:59:02] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [29/Sep/2024 10:59:02] "GET /api/data HTTP/1.1" 200 -
^C
Coverage Report:
Name Stmts Miss Branch BrPart Cover
------------------------------------------
app.py 27 23 6 1 15%
------------------------------------------
TOTAL 27 23 6 1 15%
admin@pc:~/my_flask_app$
- 要看详细报告,可以用浏览器打开下面的文件
admin@pc:~/my_flask_app$ ls -lt htmlcov/index.html
-rw-rw-r-- 1 admin admin 4929 9月 29 11:00 htmlcov/index.html
admin@pc:~/my_flask_app$
经验分享
- 如果遇到最后报错"coverage.exceptions.NoDataError: No data to report.",需要关注COV = coverage.coverage()里面的include=后面的表达式是否正确
- 不确定怎么写,可以先把include参数拿掉,看看能否出结果
- include和omit的表达式如何书写,可以参考这里