Flask后端实践 连载二十 Flask接口单元测试+测试报告+测试覆盖率

tips:

  • flask接口在工程中的单元测试、测试报告、测试覆盖率
  • 本文基于python3编写
  • 代码仓库

前言

不管喜不喜欢写测试代码,终究自己的应用程序都会被测试,自己应用程序的用户将成为测试者。在用户使用过程测试出现问题,往往都需要自己顶着压力去修改,那为何不早早将测试做好。

Flask使用unittest测试

  1. 编写Flask接口app.py
from flask import Flask, request, jsonify

app = Flask(__name__)


@app.route('/add', methods=["GET"])
def add():
    x = request.args.get("x", type=float)
    y = request.args.get("y", type=float)
    if x is None or y is None:
        return jsonify("err")
    return jsonify(x + y)


if __name__ == '__main__':
    app.run()
  1. 编写测试代码test_app.py
from app import app
import unittest


class TestAdd(unittest.TestCase):

    def setUp(self) -> None:
        app.config['TESTING'] = True # 开启测试环境
        self.app = app.test_client()  
        
    def tearDown(self) -> None:
        pass

    def test_add_no_parameters(self):
        """
        未传参数
        :return:
        """
        rp = self.app.get("/add")
        data = rp.json
        self.assertEqual(data, "err")

    def test_add_x(self):
        """
        传入x
        :return:
        """
        rp = self.app.get("/add", query_string={"x": 1, })
        data = rp.json
        self.assertEqual(data, 'err')

    def test_add_y(self):
        """
        传入y
        :return:
        """
        rp = self.app.get("/add", query_string={"y": 1, })
        data = rp.json
        self.assertEqual(data, 'err')

    def test_add_strings(self):
        """
        传入值类型错误
        :return:
        """
        rp = self.app.get("/add", query_string={"y": "x", "x": "y"})
        data = rp.json
        self.assertEqual(data, 'err')

    def test_add_ok(self):
        """
        传入值正确
        :return:
        """
        rp = self.app.get("/add", query_string={"y": 1, "x": 1})
        data = rp.json
        self.assertEqual(data, 2)


if __name__ == '__main__':
    unittest.main(verbosity=2)
  • 命令行运行python test_app.py
  • 查看未通过的测试用例,并修复错误

Flask工程模式测试

  1. 项目目录
_
 |_ app
 |  |_ __init__.py # 工程函数
 |  |_ api.py  # API接口
 |
 |_ tests
 |  |_ __init__.py
 |  |_ HTMLTestRunner.py
 |  |_ test_add.py
 |  |_ test_subtract.py
 |  |_ run_test.py
 | 
 |_ run.py # 启动Flask APP
  1. 编写工程函数__init__.py
from flask import Flask

def create_app():
    app = Flask(__name__)

    from .api import bp
    app.register_blueprint(bp)

    return app
  1. 编写API接口api.py
from flask import Blueprint, request, jsonify

bp = Blueprint("api", __name__, url_prefix='/')


@bp.route('/add', methods=["GET"])
def add():
    x = request.args.get("x", type=float)
    y = request.args.get("y", type=float)
    if x is None or y is None:
        return jsonify("err")
    return jsonify(x + y)

@bp.route('/subtract', methods=["GET"])
def subtract():
    x = request.args.get("x", type=float)
    y = request.args.get("y", type=float)
    if x is None or y is None:
        return jsonify("err")
    return jsonify(x - y)
  1. 编写测试代码test_add.pytest_subtract.py
  • test_add.py测试加法
# test_add.py
import unittest
from app import create_app


class TestAdd(unittest.TestCase):

    def setUp(self) -> None:
        app = create_app()
        app.config['TESTING'] = True
        self.app = app.test_client()

    def test_add_no_parameters(self):
        """
        未传参数
        :return:
        """
        rp = self.app.get("/add")
        data = rp.json
        self.assertEqual(data, "err")

    def test_add_x(self):
        """
        传入x
        :return:
        """
        rp = self.app.get("/add", query_string={"x": 1, })
        data = rp.json
        self.assertEqual(data, 'err')

    def test_add_y(self):
        """
        传入y
        :return:
        """
        rp = self.app.get("/add", query_string={"y": 1, })
        data = rp.json
        self.assertEqual(data, 'err')

    def test_add_strings(self):
        """
        传入值类型错误
        :return:
        """
        rp = self.app.get("/add", query_string={"y": "x", "x": "y"})
        data = rp.json
        self.assertEqual(data, 'err')

    def test_add_ok(self):
        """
        传入值正确
        :return:
        """
        rp = self.app.get("/add", query_string={"y": 1, "x": 1})
        data = rp.json
        self.assertEqual(data, 2)


if __name__ == '__main__':
    unittest.main(verbosity=2)
  • test_subtract.py测试减法
import unittest
from app import create_app


class TestSubtract(unittest.TestCase):

    def setUp(self) -> None:
        app = create_app()
        app.config['TESTING'] = True
        self.app = app.test_client()

    def test_subtract_no_parameters(self):
        """
        未传参数
        :return:
        """
        rp = self.app.get("/subtract")
        data = rp.json
        self.assertEqual(data, "err")

    def test_subtract_x(self):
        """
        传入x
        :return:
        """
        rp = self.app.get("/subtract", query_string={"x": 1, })
        data = rp.json
        self.assertEqual(data, 'err')

    def test_subtract_y(self):
        """
        传入y
        :return:
        """
        rp = self.app.get("/subtract", query_string={"y": 1, })
        data = rp.json
        self.assertEqual(data, 'err')

    def test_subtract_strings(self):
        """
        传入值类型错误
        :return:
        """
        rp = self.app.get("/subtract", query_string={"y": "x", "x": "y"})
        data = rp.json
        self.assertEqual(data, 'err')

    def test_subtract_ok(self):
        """
        传入值正确
        :return:
        """
        rp = self.app.get("/subtract", query_string={"y": 1, "x": 1})
        data = rp.json
        self.assertEqual(data, 1)


if __name__ == '__main__':
    unittest.main(verbosity=2)
  1. 测试
  • 命令行运行python test_add.pypython test_subtract.py
  • 查看未通过的测试用例,并修复错误

Flask测试报告

  1. 当有多个测试用例时,采用python的命令不方便,unittest中可以加载指定路径下符合条件的测试用例。并且可以生成Text报告run_test.py
import unittest

test_dir = './'
discover = unittest.defaultTestLoader.discover(test_dir, pattern='*test_*.py')

if __name__ == '__main__':
    with open('UnittestTextReport.txt', 'a') as f:
        runner = unittest.TextTestRunner(stream=f, verbosity=2)
        runner.run(discover)
  1. 当然也可以生成HTML报告,可以使用HTMLTestRunner,不过该包已经很久没有更新了,只能在python2的环境下使用,参考HTMLTestRunner修改成Python3版本修改之后python3环境下使用。
import unittest
from tests.HTMLTestRunner import HTMLTestRunner

test_dir = './'
discover = unittest.defaultTestLoader.discover(test_dir, pattern='*test_*.py')

if __name__ == '__main__':
    with open('HtmlReport.html', 'wb') as f:
        runner = HTMLTestRunner(stream=f, title='test report', description='', verbosity=2)
        runner.run(discover)

Flask测试覆盖率

  1. 安装coverage,pip install coverage
  2. coverage命令
    coverage命令格式coverage <command> [options] [args]

命令

含义

annotate

运行一个python程序并收集运行数据

combine

合并覆盖报告

erase

删除之前收集的统计数据

help

帮助

html

创建Html报告

report

覆盖率统计数据报告

run

运行Python程序并测量代码执行。

xml

创建Xml报告

  1. coverage覆盖报告
  • 命令行进入到项目根目录
  • 命令行输入coverage run --source='app' -m tests.run_test测试覆盖
  • 命令行输入coverage report
Name              Stmts   Miss  Cover
-------------------------------------
app\__init__.py       6      0   100%
app\api.py           14      0   100%
-------------------------------------
TOTAL                20      0   100%
  • 命令行输入coverage html,在当前目录生成文件夹htmlcov,打开文件夹中的index.html文件查看覆盖报告

总结

  • 使用unittest测试Flask接口
  • 生成测试报告和测试覆盖报告