Flask项目nose单元测试与覆盖率

flask项目可用nose进行单元测试,同时给出覆盖率结果。不用额外起http服务,还是挺方便的。
nose文档:https://nose.readthedocs.io/en/latest/usage.html

  1. 安装python包
    nose3
    coverage

  2. 在项目根目录下起一个test目录

  3. 创建init.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"""
2020.08.05 zhouzhiran
"""

from app import create_app

app = create_app('testing')
app_ctx = app.app_context()
app_ctx.push()

test_app = app.test_client()

def setUp():
print('set up')

def tearDown():
print('tear down')
  1. 创建test_base.py测试基类。在里面实现get、post等请求。可不断对其完善,添加各种请求信息,如form参数、json参数、header等等。cookie的信息要根据自己项目来抠。
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import json

from nose.tools import assert_equal
# from werkzeug.http import parse_cookie
from werkzeug.http import parse_cookie

import test

import config.config as config


class BaseTest():

def __init__(self):
print('__init__')
self.client = test.test_app
self.session = None
# self.headers = {'content-type': 'application/json'}

def check_resp_200(self, resp):
print('check_resp_200')
print(resp)
assert_equal(resp.status_code, 200)

def check_resp_400(self, resp):
print('check_resp_400')
assert_equal(resp.status_code, 400)


def post(self, path, data = None, force_login = True, json_data = None):
if force_login and self.session is None:
print('need login session')
print(self.session)
self.login()
print(self.session)
self.client.set_cookie('localhost', 'session', self.session)
if json_data is not None:
return self.client.post(path, content_type='application/json', data=json.dumps(json_data))
else:
return self.client.post(path, content_type='multipart/form-data', data=data)

def post_file(self, path, data, force_login=True):
if force_login and self.session is None:
print('need login session')
print(self.session)
self.login()
print(self.session)
self.client.set_cookie('localhost', 'session', self.session)
return self.client.post(path, content_type='multipart/form-data', data=data)

def put(self, path, data, force_login=True):
if force_login and self.session is None:
print('need login session')
print(self.session)
self.login()
print(self.session)
self.client.set_cookie('localhost', 'session', self.session)
return self.client.put(path, content_type='multipart/form-data', data=data)

def get(self, path, params, force_login=True):
print(params)
if force_login and self.session is None:
print('need login session')
print(self.session)
self.login()
print(self.session)
self.client.set_cookie('localhost', 'session', self.session)
return self.client.get(path, query_string=params)

def login(self):
payload = {
'name': config.TEST_USERNAME,
'password': config.TEST_PASSWORD
}
print(payload)
resp = self.post('/site/login', data=payload, force_login = False)
print(resp)
# print(resp.headers.get('Set-Cookie')

self.check_resp_200(resp)

cookies = resp.headers.getlist('Set-Cookie')

print(cookies)
print(('wtf2', parse_cookie(cookies[1])))
self.session = parse_cookie(cookies[1])['erp session']

print(('wtf2', self.session))
  1. 起测试代码。文件必须以test打头比如test_site_v2.py。
    需创建一个测试类,以Test打头。
    类里定义要测试的函数,以test打头。
    传文件的方法:用form参数,做一个tuple。比如 data['in_label_file'] = (open('./main.py', 'rb'), 'in_label_file')
    取返回数据的方法:order_id = json.loads(resp.data.decode('utf8'))['order_id']
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
from test.test_base import BaseTest
import json

class TestSiteV2(BaseTest):

def test_need_to_login(self):
data = {}
resp = self.post('/site/v2/role', data = data, force_login = False)

self.check_resp_400(resp)

def test_test(self):
resp = self.get('/site/v2/test', '')

self.check_resp_200(resp)

def test_001_post_order(self):
data = {'customer_id':15926}
data['payee'] = '上海合宙'
data['recipient'] = 'xc'
data['phone'] = '123456788888'
data['province'] = '上海'
data['city'] = '上海'
data['area'] = '海'
data['address'] = '海合'
data['info'] = 'test'
data['express_price'] = 1000
data['payment_type'] = 1
data['order_type'] = 0
data['products'] = json.dumps({'1':{'product_id':53, 'price':1, 'amount':10, 'in_label_file':'in_label_file', 'out_label_file':'out_label_file', 'shipment_file':'shipment_file', 'express_file':'express_file', 'package_file':'package_file', 'module_label_file':'module_label_file'}, '2':{'product_id':483, 'price':2, 'amount':10} }) # total_price_cut 暂时只能用在淘宝订单
data['in_label_file'] = (open('./main.py', 'rb'), 'in_label_file')
data['in_label_file'] = (open('./main.py', 'rb'), 'in_label_file')
data['out_label_file'] = (open('./main.py', 'rb'), 'out_label_file')
data['shipment_file'] = (open('./main.py', 'rb'), 'shipment_file')
data['express_file'] = (open('./main.py', 'rb'), 'express_file')
data['package_file'] = (open('./main.py', 'rb'), 'package_file')
data['module_label_file'] = (open('./main.py', 'rb'), 'module_label_file')

resp = self.post('/site/v2/order', data = data)


order_id = json.loads(resp.data.decode('utf8'))['order_id']
order_sn = json.loads(resp.data.decode('utf8'))['order_sn']
  1. 在根目录下运行测试命令。
    nosetests –with-coverage –cover-erase –cover-package=app.site

–cover-package为自定要测的模块。测多个模块的话用逗号隔开,比如–cover-package=app.site,app.open,app.event

  1. 结果
    会按函数名字母顺序执行函数。
    如果assert失败会停止。并打印出log。
    最后给出覆盖率统计。
    依次为测试文件名,代码总行数,未覆盖的总行数,覆盖率,所有未覆盖的代码位置。
    比如下面的结果:测试了app/site/site_v2.py,总行数1233,未覆盖行数295,覆盖率76%。
    之后是所有未覆盖的代码,比如35-36行、114-115行等等等在测试中都没跑到,修改测试代码让它能跑到即可提高覆盖率。
1
2
app/site/site_v2.py     1233    295    76%   35-36, 70, 114-115, 124, 152, 155, 220-225, 232-234, 243-244, 249-260, 269, 300, 387, 449, 463, 477, 491, 505, 519, 570, 587, 597, 613, 624-758, 855, 961, 975, 989, 1003, 1017, 1031, 1045, 1174-1178, 1188, 1287-1293, 1299-1344, 1354, 1364, 1424, 1427, 1439, 1476, 1479, 1506, 1509, 1532-1534, 1547, 1656, 1700, 1714, 1804, 1850, 1853, 1859-1860, 1935-1968, 1973-1982, 2293, 2298, 2391-2395, 2415, 2495, 2574, 2593, 2612, 2619-2622, 2627-2630, 2635-2638, 2643-2729, 2740-2741

上次更新 2021-01-28