六. pytest参数化
这一小节我们用 pytest 来断言测试用例并用 pytest-html 生成测试报告。
1. 接口加密
encryption.py 中定义了两个函数 MD5_sign 和 encryptAES,调用前者会返回一个数字签名;后者会对传入的数据使用AES算法进行加密,返回加密数据用于传输。
接口的解密已经在<Django接口开发>小节中有过叙述,这里就不重复了,有兴趣的话可以跳回去了解。
encryption.py 的具体内容如下:
1 import time
2 import hashlib
3 from Crypto.Cipher import AES
4 import base64
5
6
7 def MD5_sign():
8 """
9 生成MD5数字签名并返回
10 """
11 key = "wahaha"
12 now_time = time.time() # 获取当前时间
13 client_time = str(now_time).split('.')[0] # 转换为字符串,截取小数点前
14
15 # 生成MD5签名
16 hash = hashlib.md5()
17 sign = client_time + key
18 sign_utf8 = sign.encode(encoding='utf-8')
19 hash.update(sign_utf8)
20 sign_md5 = hash.hexdigest()
21 md5_sign = "%s|%s" %(sign_md5, client_time)
22
23 return md5_sign
24
25
26 def encryptAES(src):
27 """
28 AES加密函数
29 """
30 key = "qwertyuiopasdfgh"
31 iv = b"1234567890123456" # 初始化向量
32 block = 16 # 密码块要求16位,该数据用于计算填充补位
33 aes = AES.new(key, AES.MODE_CBC, iv) # 初始化加密器
34 # 如果s不足16位,进行填充。填充模式:PKCS#5/PKCS7
35 pad = lambda s: s + (block-len(s)%block) * chr(block-len(s)%block)
36 src = aes.encrypt(pad(src)) # 进行AES加密
37 return base64.urlsafe_b64encode(src) # 二次转换,便于传输
2. 测试
pytest 框架可以轻松编写小型测试,然后进行扩展以支持应用程序和库的复杂功能测试,并且支持参数化功能而不必像 unittest 那样再去引入专门的参数化模块。
安装pytest:pip install pytest
test_main.py:
1 import os
2 import hashlib
3 import json
4 import sys
5 import pytest
6 import requests
7 import getCase
8 import encryption
9
10
11 # 获取测试数据
12 excel = getCase.getCase().get_xls()
13
14 class TestApi(object):
15 # 装饰器,实现参数化
16 @pytest.mark.parametrize('num, api_name, description, api_host, request_url, request_method, request_data, encryption
_method, check_point, active', excel)
17 # 测试用例
18 def test_api(self, num, api_name, description, api_host, request_url, request_method, request_data, encryption_method, check_point, active):
19 # 拼接出完整请求地址
20 url = api_host.replace('\n', '').replace('\r', '') + request_url
21 # 以防万一,如果用例未激活则跳过
22 if active == "no":
23 pytest.skip("active为no,跳过该测试用例")
24 elif active == "yes":
25 # 处理GET请求
26 if request_method == "GET":
27 # 如果请求需要MD5签名
28 if encryption_method == 'MD5':
29 data = json.loads(request_data)
30 sign = encryption.MD5_sign()
31 data.update(md5_sign=sign)
32 session = requests.Session()
33 # 禁止代理服务
34 session.trust_env = False
35 r = session.get(url, params=data)
36 else:
37 session = requests.Session()
38 session.trust_env = False
39 r = session.get(url, params=request_data)
40
41 # 处理POST请求
42 elif request_method == "POST":
43 data = json.loads(request_data)
44 session = requests.Session()
45 session.trust_env = False
46 # AES加密处理
47 if encryption
_method == 'AES':
48 encoded = encryption.encryptAES(json.dumps(data)).decode()
49 r = session.post(url, data={'data': encoded})
50 # 未加密请求
51 elif encryption_method == 'no':
52 r = session.post(url, data=data)
53
54 # result保存响应值
55 result = r.json()
56 # 检查
57 assert result['status'] == int(check_point.split(':', 1)[0])
58 assert result['message'] == check_point.split(':', 1)[1]
现在终于可以运行一下测试用例啦:
>>> pytest -q test_main.py
....... [100%]
=============================================== warnings summary ================================================
/usr/lib/python3.7/site-packages/requests/__init__.py:91
/usr/lib/python3.7/site-packages/requests/__init__.py:91: RequestsDependencyWarning: urllib3 (1.25.2) or chardet (3.0.4) doesn't match a supported version!
RequestsDependencyWarning)
-- Docs: https://docs.pytest.org/en/latest/warnings.html
7 passed, 1 warnings in 0.37 seconds
需要注意的是,这份测试用例是不严谨的。作为一份Demo,它仅搭建了一个演示架构,内部填充需要根据产品的实际情况做出调整。
3. 测试报告
pytest-html 是pytest的一个插件,它为测试结果生成一个HTML报告。
安装:pip install pytest-html
为了遵守内容安全策略(CSP),默认情况下,pytest --html=report.html
命令生成的测试报告会单独存储CSS和图像等多个资源。您也可以创建一个独立的报告,在共享结果时更方便。这可以通过以下命令完成:
pytest --html = report.html --self-contained-html
现在我们试着生成一份独立的测试报告:
>>> pytest -q ./test_main.py --html=../Report/haha.html --self-contained-html
通过上面这条命令我们在 IntTestDemo/Report/ 目录下生成了一份名为 haha.html 的测试报告,可以用浏览器打开看一下:
在 App 目录下新建一个文件 conftest.py ,内容如下:
1 from datetime import datetime
2 from py.xml import html
3 import pytest
4
5 @pytest.mark.optionalhook
6 def pytest_html_results_table_header(cells):
7 #cells.insert(1, html.th('Time', class_='sortable time', col='time'))
8 cells.insert(3, html.th('Params'))
9 cells.insert(2, html.th('Description'))
10 cells.insert(0, html.th('No.'))
11 cells.pop()
12
13 @pytest.mark.optionalhook
14 def pytest_html_results_table_row(report, cells):
15 #cells.insert(1, html.td(datetime.utcnow(), class_='col-time'))
16 cells.insert(3, html.td(report.params))
17 cells.insert(2, html.td(report.description))
18 cells.insert(0, html.td(report.number))
19 cells.pop()
20
21 @pytest.mark.hookwrapper
22 def pytest_runtest_makereport(item, call):
23 outcome = yield
24 report = outcome.get_result()
25 # 对test一列重新编码,显示中文
26 report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape")
27 # test列中包含了测试用例传入的所以参数,先对其进行分割
28 result = report.nodeid.split('-')
29 # 从test列剥离出不同的参数信息
30 number = result[0].split('[')[1]
31 api_name = result[1]
32 description = result[2]
33 url = result[4]
34 request_method = result[5]
35 params = result[6]
36 check_point = result[8]
37 # 对新插入的表格进行赋予参数值
38 report.nodeid = api_name + ' -- ' + request_method + ' -- ' + url
39 report.description = str(description)
40 report.number = str(number)
41 report.url = str(url)
42 report.params = str(params)
再次生成测试报告看看:
>>> pytest -q ./test_main.py --html=../Report/haha.html --self-contained-html
4. 又是愉快的一天
>>> cd ../ # 回到根目录IntTestDemo
>>> git add . # 将项目改动放入暂存区
>>> git commit -m "add test_main.py and we can genarate test report now" # 将暂存区的的修改提交到当前分支,m参数表示添加注释
>>> git push origin master # 推送到远程服务器(github)