单元测试,主要是为了测试某个方法,或是某个代码快,对于各种输入的处理,输出是否符合预期。但由于其他库、或模块的依赖,以至于很难独立测试我们自己实现的逻辑代码。
对此,引出 mock。
一、Flask
Flask是个轻量 API 框架,使用起来非常容易上手
1 | # 安装:pip install flask |
这样,一个简单的 server 就跑起来了,访问 http:localhost:5000 便可以看到返回的数据:Hello Flask
下面举例说明,如果对单一的接口写测试用例
二、举例:用户登录
- 用户登录是个常见的功能接口,接口逻辑之外的部分基本同上,这里省略不写。用户使用 name 和 password 进行登陆操作,服务器收到请求后,根据 name 从数据库查询 password ,一致则返回 200 OK,不一致返回 400 Bad Request,很简单的实现,如下: 其中 UserDB 为数据模块中,从数据库查询用户数据的类。这里对于登录逻辑的单元测试,只指测试该部分最小的代码块,对于代码块中引入的依赖,在测试时都认为是正常的。例如,在测试 login() 的时候,我们认为 UserDB 是正常的、可用的,至于 UserDB 的可靠性,需要 UserDB 模块的单元测试来保障。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19from flask import request
from app.model import UserDB
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
对于待测试模块内引入的依赖,采用 mock 的方式模拟。 - 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
46import 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)
此外,还可以对测试缺少参数,这里不再赘述。这样,便可对接口的各种情况进行测试了。