第 3 章 辅助函数(debugtalk.py)
3.1 参数关联(extract)
如何将上个登录接口的返回token,传给下个接口当做请求参数?
参数关联场景:
1.登录返回token -> 2.下个接口头部传token
extract 提取变量
$token 引用变量
一般在提取变量后,需在 config 中 export 导出变量 导出变量的目的是,使他变成全局变量,方便后面步骤调用 (在一个 yml 用例 引用另外一个 yml 用例的时候用得到)
3.2 环境变量 .env
在自动化测试项目的根目录中,创建 .env 文件, 并将敏感数据信息放置到其中,存储采用 name=value 的格式 (数据库配置信息等)
如:
username=test
password=123456
base_url=http://49.235.92.12:8201
项目根目录新建 .env 保存测试数据
引用环境变量 ${ENV(key)}
3.3 Testcaes 引用 Testcase
用例1 登录 export导出提取的token,方面后面引用
引用testcase:yml case相对路径
被引用的testcase中需要提取下一个case要用到的参数
3.4 场景案例 – 参数化和数据驱动
参数化在 config 中使用 parameters 关键字
Httprunner 2.x是在 testsuite 中实现参数化
Httprunner 3.x是在 testcase 的 Config 中实现参数化
3.4.1 单个参数参数化
准备四组登录用的账号和密码账号为test1,test2,test3,test4,密码统一设置为123456。
3.4.2 多个参数一一对应 - 参数化![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/0b5a87b52970c665659c808814079f8b.png)
3.4.3 笛卡尔积 组合参数
比如测试账号有四种[“test1”, “test2”, “test3”, “test4”],密码也有四种 [“123456”, “123456”, “123456”, “123456”] 用笛卡尔积组合的话,就是4*4=16种组合
没办法控制测试结果,一半用于测试结果是一样的测试场景
参数化
httprunner 参数化数据源指定支持三种方式:
- 在 YAML/JSON 中直接指定参数列表: 该种方式最为简单易用,适合参数列表比较小的情况
- 通过内置的 parameterize(可简写为P)函数引用 CSV 文件:该种方式需要 准备 CSV 数据文件,适合数据量比较大的情况
- 调用 debugtalk.py 中自定义的函数生成参数列表:该种方式最为灵活,可通 过自定义 Python 函数实现任意场景的数据驱动机制,当需要动态生成参数列表 时也需要选择该种方式 本节讲解内置的 parameterize(可简写为P)函数引用 CSV 文件
3.5 参数化引用CSV文件
3.5.1 yml 文件引用CSV文件
data/user_password.csv 文件存测试数据 user, psw
yml 文件引用 data/user_password.csv 文件 ${P(data/xx.csv)} 引用测试数据文件,可以支持相对路径,以项目根目录(debugtalk.py)为节点
3.6 参数化引用 debugtalk 函数
登录参数化案例 debugtalk.py生成测试数据
testcase 里面 yml 文件引用函数 ${get_account(4)}
test1-test4 账号是提前注册好的
3.7 注册案例(随机变量)
需求:自动化需要多次反复运行注册用例
遇到问题:注册账号,同一个账号只能注册一次
解决思路:
1.注册账号加时间戳(随机变量)
2.连数据库先删掉此账号
1.注册账号加时间戳(随机变量):
Debugtalk.py写个函数,生成带时间戳的用户名
yml 引用函数${register_user()}
3.8 hook机制(setup和teardown)
HttpRunner3.x 是基于 python Pytest 框架,里面也有前置 和后置 的概念。
setup_hooks(函数名称不是固定的): 开始执行前触发 hook 函数,主要用于请求预处理(签名,加密)。
teardown_hooks: 结束执行后触发 hook 函数,主要用于返回预处理(解密)。
在debugtalk.py写2个简单函数
带上-s查看详细日志 hrun testcase/part3/login_hook.yml -s
request是请求对象,req_json是对应的
3.9 sign签名案例
在请求的body中加一个sign参数,是一个加密的字符串
# debugtalk.py
import hashlib
def sign_body(body, apikey="12345678"):
'''请求body sign签名'''
# 列表生成式,生成key=value格式
a = ["".join(i) for i in body.items() if i[1] and i[0] != "sign"]
# print(a)
# 参数名ASCII码从小到大排序
strA = "".join(sorted(a))
# print(strA)
# 在strA后面拼接上apiKey得到striSignTemp字符串
striSignTemp = strA+apikey
# 将strSignTemp字符串转换为小写字符串后进行MD5运算
# MD5加密
def jiamimd5(src):
m = hashlib.md5()
m.update(src.encode('UTF-8'))
return m.hexdigest()
sign = jiamimd5(striSignTemp.lower())
# print(sign)
return sign
def login_sign(request):
body = request.get("req_json")
print("签名前body:", body)
sign = sign_body(body=body, apikey="12345678")
print("sign:", sign)
request["req_json"]["sign"] = sign
print("request:", request)
config:
name: 用例描述-登录用例
base_url: http://49.235.92.12:8201
variables:
user: test
psw: "123456"
export:
- token
teststeps:
-
name: step-登录
setup_hooks:#$request 内置变量
- ${login_sign($request)}
request:
url: /api/v3/login
method: POST
json:
username: $user
password: $psw
sign: xxxxx
extract:
token: body.token
validate:
- eq: [status_code, 200]
- eq: [body.msg, login success!]
- eq: [body.code, 0]
- eq: [body.username, $user]
- len_eq: [body.token, 40]
3.10文件上传
upload关键字上传文件
图片放data目录,相对路径引用图片地址
3.11 连接数据库增删改查
pip install PyMysql==0.9.3
需求:接口操作完成后,校验数据库校验数据库,需用python连接数据库增删改查
项目下新建utils目录,添加连接数据库公共操作
import pymysql
'''
pip install PyMySQL==0.9.3
'''
# 配置数据库相关信息
dbinfo = {
"host": "xx.xxx.xx.xx",
"user": "root",
"password": "123456",
"port": 3309}
class DbConnect(object):
def __init__(self, db_cof, database=""):
# 打开数据库连接
self.db = pymysql.connect(database=database,
cursorclass=pymysql.cursors.DictCursor,
**db_cof)
# 使用cursor()方法获取操作游标
self.cursor = self.db.cursor()
def select(self, sql):
# SQL 查询语句
# sql = "SELECT * FROM EMPLOYEE \
# WHERE INCOME > %s" % (1000)
self.cursor.execute(sql)
results = self.cursor.fetchall()
return results
def execute(self, sql):
# SQL 删除、提交、修改语句
# sql = "DELETE FROM EMPLOYEE WHERE AGE > %s" % (20)
try:
self.cursor.execute(sql) # 执行SQL语句
self.db.commit() # 提交修改
except:
# 发生错误时回滚
self.db.rollback()
def close(self):
# 关闭连接
self.db.close()
if __name__ == '__main__':
db = DbConnect(dbinfo, database="apps")
# 查询
sql1 = 'SELECT * from apiapp_goods WHERE id = 1;'
res1 = db.select(sql1)
print(res1)
# 执行
sql2 = 'UPDATE apiapp_goods SET goodsname="xx" WHERE id=1;'
db.execute(sql2)
db.close()
修改商品信息接口,修改商品状态后
校验数据库对应的goodsstatus状态有没更新成功
①先调用接口修改商品
config:
name: 用例描述-修改商品用例
base_url: http://49.235.92.12:8201
variables:
sp_id: 1
goodsname: yy
goodscode: "sp_100861117"
merchantid: "10001"
merchantname: "hh"
goodsprice: 99.9
stock: 100
goodsgroupid: 0
goodsstatus: 1
price: 21.0
teststeps:
-
name: 修改商品
request:
url: /api/v1/goods/$sp_id
method: PUT
json:
goodsname: $goodsname
goodscode: $goodscode
merchantid: $merchantid
merchantname: $merchantname
goodsprice: $goodsprice
stock: $stock
goodsgroupid: $goodsgroupid
goodsstatus: $goodsstatus
price: $price
validate:
- eq: [status_code, 200]
- eq: [body.code, 0]
- eq: [body.msg, success!]
- eq: [body.data.goodsstatus, '${get_db_goods($sp_id, goodsstatus)}'] #引用函数
②在debugtalk.py根据商品id查询商品对应字段key
def get_db_goods(sp_id=1, key="goodsstatus"):
db = DbConnect(dbinfo, database="apps")
sql1 = 'SELECT * from apiapp_goods WHERE id = %s;' % sp_id
res1 = db.select(sql1)
print(res1)
if len(res1) == 0:
result = ''
else:
result = res1[0][key]
return result