天天看点

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

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

对此,引出 mock。

一、Flask

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

# 安装: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. 用户登录是个常见的功能接口,接口逻辑之外的部分基本同上,这里省略不写。用户使用 name 和 password 进行登陆操作,服务器收到请求后,根据 name 从数据库查询 password ,一致则返回 200 OK,不一致返回 400 Bad Request,很简单的实现,如下:
    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 的单元测试,先看代码
    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)
               
    此外,还可以对测试缺少参数,这里不再赘述。这样,便可对接口的各种情况进行测试了。

(完)