从零搭建接口测试框架(六)——测试与测试报告

六. pytest参数化

这一小节我们用 pytest 来断言测试用例并用 pytest-html 生成测试报告。

1. 接口加密

encryption.py 中定义了两个函数 MD5_signencryptAES,调用前者会返回一个数字签名;后者会对传入的数据使用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 的测试报告,可以用浏览器打开看一下:

测试报告.png
这样的报告显然是不符合要求的,一来中文没能显示,二来格式也不够清晰,所以我们还需要定制一下测试报告,官网上有相关例子可以作为参考。
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

定制后的测试报告.png

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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容