oynix

于无声处听惊雷,于无色处见繁花

Flask API 单元测试 unittest,mock && patch

单元测试,主要是为了测试某个方法,或是某个代码快,对于各种输入的处理,输出是否符合预期。但由于其他库、或模块的依赖,以至于很难独立测试我们自己实现的逻辑代码。

对此,引出 mock。

一、Flask

Flask是个轻量 API 框架,使用起来非常容易上手

1
2
3
4
5
6
7
8
9
10
# 安装:pip install flask
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
return 'Hello Flask'

app.run(port=5000)

这样,一个简单的 server 就跑起来了,访问 http:localhost:5000 便可以看到返回的数据:Hello Flask

下面举例说明,如果对单一的接口写测试用例

二、举例:用户登录

  1. 用户登录是个常见的功能接口,接口逻辑之外的部分基本同上,这里省略不写。用户使用 namepassword 进行登陆操作,服务器收到请求后,根据 name 从数据库查询 password ,一致则返回 200 OK,不一致返回 400 Bad Request,很简单的实现,如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    from flask import request
    from app.model import UserDB

    @app.route('/login')
    def login():
    name = request.args.get('name')
    if not name:
    return 'name is required', 400

    password = request.args.get('password')
    if not password:
    return 'password is required', 400

    # 从数据库获取用户数据
    user = UserDB.get_user(name)
    if user.get('password') == password:
    return 'OK', 200
    else:
    return 'password is wrong', 400
    其中 UserDB 为数据模块中,从数据库查询用户数据的类。这里对于登录逻辑的单元测试,只指测试该部分最小的代码块,对于代码块中引入的依赖,在测试时都认为是正常的。例如,在测试 login() 的时候,我们认为 UserDB 是正常的、可用的,至于 UserDB 的可靠性,需要 UserDB 模块的单元测试来保障。
    对于待测试模块内引入的依赖,采用 mock 的方式模拟。
  2. Flask 的单元测试,先看代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    import unittest
    from unittest.mock import Mock
    from unittest.mock import patch
    # 该app为创建的Flask实例
    from application import app

    from app.model import UserDB

    class LoginTestCase(unittest.TestCase):

    def setUp(self):
    # push一个上下文,便可以使用flask中的全局变量,如g
    app.app_context().push()
    app.testing = True
    # 测试用的http client
    self.client = app.test_client()

    def test_login_success(self):
    # 真实请求中的url,host和port可省略
    url = '/login?name=flask&password=flaskpassword'
    # 模拟的方法名称,也可直接写字符串: get_user
    func_name = UserDB.get_user.__name__
    # 模拟的方法,不管请求参数是什么,都会返回return_value的值(Mock还有其他用法)
    mock_func = Mock(return_value={'name': 'flask', 'password': 'flaskpassword'})
    # patch意为,当UserDB的get_user方法被调用时,用mock出来的func来处理
    # 而mock的func,不管请求参数,都会返回return_value
    # 故而,只要UserDB的get_user被调用,都会返回{'name': 'flask', 'password': 'flaskpassword'}
    # with,表示这种处理方式的作用范围
    # 当在with的范围之外时,调用UserDB的get_user不受mock影响,会正常调用
    with patch.object(UserDB, func_name, func):
    # response为返回的响应
    response = self.client.get(url)
    # 因为传入的name和password,和UserDB的mock func返回的name和password相同
    # 所以,该请求会返回200
    # assertEqual意为,认定返回码与200相等,若不等则该用例不通过
    self.assertEqual(response.status_code, 200)

    def test_login_failed(self):
    # 测试传入错误密码的情况
    url = '/login?name=flask&password=wrongpassword'
    func_name = UserDB.get_user.__name__
    mock_func = Mock(return_value={'name': 'flask', 'password': 'flaskpassword'})
    with patch.object(UserDB, func_name, func):
    response = self.client.get(url)
    # 因为传入密码错误,所以在此我们认定返回码是400
    self.assertEqual(response.status_code, 400)

此外,还可以对测试缺少参数,这里不再赘述。这样,便可对接口的各种情况进行测试了。

------------- (完) -------------
  • 本文作者: oynix
  • 本文链接: https://oynix.com/2019/12/1266f364267c/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道