一、类之间继承问题:
由于在test_api.py定义了全局变量,而test.user.py想使用这些全局变量就得导入test_api.py模块
但是因为test_user.py导入了test_api.py的模块,到时候执行test_user.py的时候会把test.api.py中的方法(用例)也执行一遍
#演示:
目录结构:
test_api.py
import requests
class TestRequest():
#全局变量,类变量,通过类名调用
access_token=""
sess=requests.session() #定义全局变量sess=requests.session(),将所有的requests都替换为requests.session()就相当于都在一个会话中了
# 获取接口统一鉴权码token
def test_token(self):
url = "http://47.108.153.47:8089/qzcsbj/user/login"
shuju={
"username": "ddance",
"password": "000000"
}
res=TestRequest.sess.request(method="post",url=url,json=shuju) #将所有的requests都替换为requests.session()就相当于都在一个会话中了
print(res.json())
TestRequest.access_token=res.json()["data"]["token"] #将token的值赋给全局变量access_token
#修改一个商品
def test_edit_product(self):
url = "http://47.108.153.47:8089/qzcsbj/product/update"
shuju = {
"product": {
"id": 381,
"price": 20, #将价格改为20元
"productName": "狗粮"
},
"token": TestRequest.access_token
}
res=TestRequest.sess.request(method="post",url=url,json=shuju) #将所有的requests都替换为requests.session()就相当于都在一个会话中了
print(res.json())
#文件上传
def test_file_upload(self):
shuju = {
"name": "01"
}
shuju1={
"uploadfile":open(r"C:\01.jpg","rb"), #r:转义 rb:转化为只读的二进制流
}
url = "http://47.108.153.47:8089/qzcsbj/file/upload?name=" + shuju["name"] #接口文档中规定要将name的值跟在url之后,因此这里使用字典key提取name的值01拼接到url之后
res=TestRequest.sess.request(method="post",url=url,json=shuju,files=shuju1) #上传文件的时候使用files而不是data或者json #将所有的requests都替换为requests.session()就相当于都在一个会话中了
print(res.json())
test_user.py
import requests
from testcases.test_api import TestRequest #导入testcases包下的test_api模块下的TestRequest类
class TestUser():
#修改用户信息
def test_edit_user(self):
shuju={
"token": TestRequest.access_token, #使用TestRequest类的全局变量access_token
"user": {
"id": 1305,
"username": "ddance",
"password": "000000",
"realName": "string",
"sex": "0",
"birthday": "2001-01-01",
"phone": "13542588888", #修改电话号码为13542588888
"utype": "0",
"addtime": "2023-05-12 22:21:56.0",
"adduser": "ddance"
}
}
url = "http://47.108.153.47:8089/qzcsbj/user/update"
res=requests.request(method="post",url=url,json=shuju)
print(res.json())
all.py
import pytest
if __name__ == '__main__':
pytest.main()
执行结果:
D:\JetBrains\PycharmProjects\API接口测试\venv\Scripts\python.exe D:\JetBrains\PycharmProjects\API接口测试\all.py
============================= test session starts =============================
testcases\test_api.py ... [ 42%] #这里3个“.”表示执行了3个用例
testcases\test_user.py .... [100%] #这里4个“.”表示执行了4个用例
============================== 7 passed in 0.93s ==============================
Process finished with exit code 0
为什么test_user.py中明明一个用例却执行了4个用例呢?这是因为test_user.py调用test_api.py下的类的时候把test_api.py类下的3个用例也执行了一遍
如何避免这种错误呢,可以去掉全局变量,用YAML文件代为保存全局变量:
#演示:
目录结构:#新增一个ymal空文件extract.yaml,使用yaml_util.py模块将test_api.py产生的全局变量access_token存储到extract.yaml,test_user.py可以在extract.yaml中提取使用access_token
yaml_util.py
import yaml #需要安装模块:pip install pyyaml
import os
#读取
def read_yaml(key):
with open(os.getcwd()+'/extract.yaml',mode='r',encoding='utf-8') as f: #os.getcwd()方法用于返回当前工作目录 #mode='a':追加|'w':覆盖|'r':只读 #encoding编码格式
print('这里是打印'+os.getcwd()+'/extract.yaml')
value=yaml.load(stream=f,Loader=yaml.FullLoader) #yaml.load()读取yaml文件 #stream=f要读取的对象 #Loader=yaml.FullLoader全部提取
return value[key] #value是字典类型的,可以通过关键字方式输出值
#写入
def write_yaml(data):
with open(os.getcwd()+'/extract.yaml',mode='a',encoding='utf-8') as f:
yaml.dump(data,stream=f,allow_unicode=True) #yaml.dump()写入数据到yaml文件 #allow_unicode=True允许unicode方式
#清空
def clear_yaml():
with open(os.getcwd()+'/extract.yaml',mode='w',encoding='utf-8') as f:
f.truncate() #清空文件内容
test_api.py #1、导入yaml_util.py模块 2、删除全局变量access_token 3、修改test_token用例中保存token的方式(存到文件中) 4、修改test_edit_product用例中获取token的方式(从文件中读取)
import requests
from common.yaml_util import read_yaml,write_yaml #修改点1
class TestRequest():
#全局变量,类变量,通过类名调用
##删除access_token="" #修改点2
sess=requests.session() #定义全局变量sess=requests.session(),将所有的requests都替换为requests.session()就相当于都在一个会话中了
# 获取接口统一鉴权码token
def test_token(self):
url = "http://47.108.153.47:8089/qzcsbj/user/login"
shuju={
"username": "ddance",
"password": "000000"
}
res=TestRequest.sess.request(method="post",url=url,json=shuju) #将所有的requests都替换为requests.session()就相当于都在一个会话中了
print(res.json())
write_yaml({"access_token":res.json()["data"]["token"]}) #将{"access_token":token的值}键值对写入extract.yaml文件 #修改点3
#修改一个商品
def test_edit_product(self):
url = "http://47.108.153.47:8089/qzcsbj/product/update"
shuju = {
"product": {
"id": 381,
"price": 20, #将价格改为20元
"productName": "狗粮"
},
"token": read_yaml("access_token") #读取extract.yaml文件中key为access_token对应的值 #修改点4
}
res=TestRequest.sess.request(method="post",url=url,json=shuju) #将所有的requests都替换为requests.session()就相当于都在一个会话中了
print(res.json())
#文件上传
def test_file_upload(self):
shuju = {
"name": "01"
}
shuju1={
"uploadfile":open(r"C:\01.jpg","rb"), #r:转义 rb:转化为只读的二进制流
}
url = "http://47.108.153.47:8089/qzcsbj/file/upload?name=" + shuju["name"] #接口文档中规定要将name的值跟在url之后,因此这里使用字典key提取name的值01拼接到url之后
res=TestRequest.sess.request(method="post",url=url,json=shuju,files=shuju1) #上传文件的时候使用files而不是data或者json #将所有的requests都替换为requests.session()就相当于都在一个会话中了
print(res.json())
test_user.py #1、删除导入test_api.py中全局变量的方法 2、导入yaml_util.py模块 3、修改test_edit_user用例中获取token的方式(从文件中读取)
import requests
##删除from testcases.test_api import TestRequest #导入testcases包下的test_api模块下的TestRequest类 #修改点1
from common.yaml_util import read_yaml,write_yaml #修改点2
class TestUser():
#修改用户信息
def test_edit_user(self):
shuju={
"token": read_yaml("access_token"), #读取extract.yaml文件中key为access_token对应的值 #修改点3
"user": {
"id": 1305,
"username": "ddance",
"password": "000000",
"realName": "string",
"sex": "0",
"birthday": "2001-01-01",
"phone": "13542588888", #修改电话号码为13542588888
"utype": "0",
"addtime": "2023-05-12 22:21:56.0",
"adduser": "ddance"
}
}
url = "http://47.108.153.47:8089/qzcsbj/user/update"
res=requests.request(method="post",url=url,json=shuju)
print(res.json())
all.py
import pytest
if __name__ == '__main__':
pytest.main(['-vs'])
执行结果:
D:\JetBrains\PycharmProjects\API接口测试\venv\Scripts\python.exe D:\JetBrains\PycharmProjects\API接口测试\all.py
============================= test session starts =============================
testcases/test_api.py::TestRequest::test_token {'code': 2003, 'msg': '登录成功', 'data': {'token': 'ddance_c08566ea-529b-4817-a8cf-edf20a3da6e8'}}
PASSED
testcases/test_api.py::TestRequest::test_edit_product {'code': 3005, 'msg': '更新商品成功', 'data': {'id': 381, 'productName': '狗粮', 'price': 20.0}}
PASSED
testcases/test_api.py::TestRequest::test_file_upload {'code': 7001, 'msg': '文件上传成功', 'data': '01_01.jpg_4d37c6cc-c3e1-4f46-9fa4-bfd9153a6e34.jpg'}
PASSED
testcases/test_user.py::TestUser::test_edit_user {'code': 2009, 'msg': '更新用户成功', 'data': {'id': 1305, 'username': 'ddance', 'password': '******', 'realName': 'string', 'sex': '0', 'birthday': '2001-01-01', 'phone': '13542588888', 'utype': '0', 'addtime': '2023-05-12 22:21:56.0', 'adduser': 'ddance'}}
PASSED
============================== 4 passed in 0.23s ==============================
Process finished with exit code 0
extract.yaml #生成了一条token的值
access_token: ddance_c08566ea-529b-4817-a8cf-edf20a3da6e8
以上代码仍有问题,由于write.yaml()选择追加的方式写入数据,多次执行后extract.yaml文件中会有多个access_token:[value]键值对,到时候read.yaml()取值的时候可能会报错,解决办法是增加前后置方法在每次写入文件前清空extract.yaml文件
#演示:
目录结构:#增加前后置固件
conftest.py
import pytest
from common.yaml_util import write_yaml,read_yaml,clear_yaml
#在所有接口请求之前执行
@pytest.fixture(scope='package',autouse=True) #作用域是包,即testcases目录,并且自动执行,其余文件无需任何修改
def clear_extract():
clear_yaml() #这是一个前置
如果test_user.py中用例需要test_api.py中用例的cookie怎么办呢?可以调用test_api.py中全局变量sess=requests.session()使两个文件中的用例处于同一个会话(session),但是这样同样会因为类之间继承的问题导致test_api.py被调用时再执行一次,怎么解决呢,可以使用统一请求封装
将统一会话请求封装到一个包中,然后所有测试用例去调用这个包中的统一请求方法
#演示:
目录结构:#新增文件
request_util.py
import requests
class RequestUtil():
#全局变量,类变量,通过类名调用
sess=requests.session()
def send_request(self,method,url,**kwargs):
method=str(method).lower() #防止用户在传入get|post写为大写,可以用lower()转化为小写
res=RequestUtil.sess.request(method=method,url=url,**kwargs)
return res
test_api.py
import requests
from common.yaml_util import read_yaml,write_yaml
from common.request_util import RequestUtil #修改点1
class TestRequest():
##删除#全局变量,类变量,通过类名调用
##删除sess=requests.session() #定义全局变量sess=requests.session(),将所有的requests都替换为requests.session()就相当于都在一个会话中了
# 获取接口统一鉴权码token
def test_token(self):
url = "http://47.108.153.47:8089/qzcsbj/user/login"
shuju={
"username": "ddance",
"password": "000000"
}
res = RequestUtil().send_request(method="post", url=url, json=shuju) #修改点2 #RequestUtil在request_util.py中是个类,因此调用其中方法时需要先实例化RequestUtil(RequestUtil后面得带上括号)
print(res.json())
write_yaml({"access_token":res.json()["data"]["token"]}) #将{"access_token":token的值}键值对写入extract.yaml文件
#修改一个商品
def test_edit_product(self):
url = "http://47.108.153.47:8089/qzcsbj/product/update"
shuju = {
"product": {
"id": 381,
"price": 20, #将价格改为20元
"productName": "狗粮"
},
"token": read_yaml("access_token") #读取extract.yaml文件中key为access_token对应的值
}
res = RequestUtil().send_request(method="post", url=url, json=shuju,headers=None) #修改点3
print(res.json())
#文件上传
def test_file_upload(self):
shuju = {
"name": "01"
}
shuju1={
"uploadfile":open(r"C:\01.jpg","rb"), #r:转义 rb:转化为只读的二进制流
}
url = "http://47.108.153.47:8089/qzcsbj/file/upload?name=" + shuju["name"] #接口文档中规定要将name的值跟在url之后,因此这里使用字典key提取name的值01拼接到url之后
res = RequestUtil().send_request(method="post", url=url, json=shuju, files=shuju1) #修改点4
print(res.json())
test_user.py
import requests
from common.yaml_util import read_yaml,write_yaml
from common.request_util import RequestUtil #修改点1
class TestUser():
#修改用户信息
def test_edit_user(self):
shuju={
"token": read_yaml("access_token"), #读取extract.yaml文件中key为access_token对应的值
"user": {
"id": 1305,
"username": "ddance",
"password": "000000",
"realName": "string",
"sex": "0",
"birthday": "2001-01-01",
"phone": "13542588888", #修改电话号码为13542588888
"utype": "0",
"addtime": "2023-05-12 22:21:56.0",
"adduser": "ddance"
}
}
url = "http://47.108.153.47:8089/qzcsbj/user/update"
res = RequestUtil().send_request(method="post", url=url, json=shuju) #修改点2
print(res.json())
all.py
import pytest
if __name__ == '__main__':
pytest.main(['-vs'])
#其余文件不变
执行结果:
D:\JetBrains\PycharmProjects\API接口测试\venv\Scripts\python.exe D:\JetBrains\PycharmProjects\API接口测试\all.py
============================= test session starts =============================
testcases/test_api.py::TestRequest::test_token {'code': 2003, 'msg': '登录成功', 'data': {'token': 'ddance_deec7593-72fa-4efb-8a8d-30cd46a76c90'}}
PASSED
testcases/test_api.py::TestRequest::test_edit_product {'code': 3005, 'msg': '更新商品成功', 'data': {'id': 381, 'productName': '狗粮', 'price': 20.0}}
PASSED
testcases/test_api.py::TestRequest::test_file_upload {'code': 7001, 'msg': '文件上传成功', 'data': '01_01.jpg_9a6dab59-5d5e-428b-9c81-7afa956d0c32.jpg'}
PASSED
testcases/test_user.py::TestUser::test_edit_user {'code': 2009, 'msg': '更新用户成功', 'data': {'id': 1305, 'username': 'ddance', 'password': '******', 'realName': 'string', 'sex': '0', 'birthday': '2001-01-01', 'phone': '13542588888', 'utype': '0', 'addtime': '2023-05-12 22:21:56.0', 'adduser': 'ddance'}}
PASSED
============================== 4 passed in 0.22s ==============================
Process finished with exit code 0
实际测试环境使用pytest的时候还需要建立数据驱动,所谓数据驱动就是把test_api.py、test_user.py中test_token、test_edit_user等等方法中写的method、url、shuju等变量的内容封装到一个yaml文件中去调用,而不是直接写死到方法中,这个yaml文件就是数据驱动
数据驱动需要用到装饰器@pytest.mark.parametrize(args_name,args_value) #args_name 参数名,字符串,可自定义名称 #args_value 参数值(list,tuple,字典列表,字典元组),有多少个值那么测试用例就会执行多少次(以"-"为分隔符计算有多少个值)
#YAML介绍: yaml是一种数据格式,主要用于配置文件或者编写用例 yaml只有两种数据: 1.键值对 key:(空格)value 2.list 用一个“-”表示一个列表 操作yaml的第三方模块是pyyaml |
#演示:
目录结构:#新增文件
test_api-test_token.yaml
- #这个"-"是每组数据的分隔符
name: 获取接口统一鉴权码token #本组数据名称
request: #请求内容
method: post #请求方法
url: http://47.108.153.47:8089/qzcsbj/user/login #请求url
shuju: #请求体
username: ddance
password: '000000' #000000加上引号表示是字符串,防止被识别成int,int只有一位0
validate: None #断言,这里选择不使用
#name、request、validate是数据驱动最基本的内容,当然这三个基本内容的名称可自定义
yaml_util.py #增加一个读取数据驱动的方法read_testcases
import yaml #需要安装模块:pip install pyyaml
import os
#读取
def read_yaml(key):
with open(os.getcwd()+'/extract.yaml',mode='r',encoding='utf-8') as f: #os.getcwd()方法用于返回当前工作目录 #mode='a':追加|'w':覆盖|'r':只读 #encoding编码格式
value=yaml.load(stream=f,Loader=yaml.FullLoader) #yaml.load()读取yaml文件 #stream=f要读取的对象 #Loader=yaml.FullLoader全部提取
return value[key] #value是字典类型的,可以通过关键字方式输出值
#写入
def write_yaml(data):
with open(os.getcwd()+'/extract.yaml',mode='a',encoding='utf-8') as f:
yaml.dump(data,stream=f,allow_unicode=True) #yaml.dump()写入数据到yaml文件 #allow_unicode=True允许unicode方式
#清空
def clear_yaml():
with open(os.getcwd()+'/extract.yaml',mode='w',encoding='utf-8') as f:
f.truncate() #清空文件内容
#读取数据驱动
def read_testcases(yaml_name):
with open(os.getcwd()+'/testcases'+'/'+yaml_name,mode='r',encoding='utf-8') as f:
value=yaml.load(stream=f,Loader=yaml.FullLoader)
return value
test_user.py #为演示方便把这个文件内容注释掉
test_api.py #为演示方便只保留一个测试用例(test_token()方法)演示
import requests
from common.yaml_util import read_yaml,write_yaml,read_testcases #修改点1
from common.request_util import RequestUtil
import pytest #修改点2
class TestRequest():
# 获取接口统一鉴权码token
@pytest.mark.parametrize("args_name",read_testcases("test_api-test_token.yaml")) #修改点3 #args_value的值是read_testcases读取yaml中的内容 #args_name返回的是字典类型
def test_token(self,args_name):
url = args_name["request"]["url"] #从args_name中获取url
shuju=args_name["request"]["shuju"] #从args_name中获取shuju
res = RequestUtil().send_request(method=args_name["request"]["method"], url=url, json=shuju) #从args_name中获取method
print(res.json())
write_yaml({"access_token":res.json()["data"]["token"]}) #将{"access_token":token的值}键值对写入extract.yaml文件
'''
#修改一个商品
def test_edit_product(self):
url = "http://47.108.153.47:8089/qzcsbj/product/update"
shuju = {
"product": {
"id": 381,
"price": 20, #将价格改为20元
"productName": "狗粮"
},
"token": read_yaml("access_token") #读取extract.yaml文件中key为access_token对应的值
}
res = RequestUtil().send_request(method="post", url=url, json=shuju,headers=None)
print(res.json())
#文件上传
def test_file_upload(self):
shuju = {
"name": "01"
}
shuju1={
"uploadfile":open(r"C:\01.jpg","rb"), #r:转义 rb:转化为只读的二进制流
}
url = "http://47.108.153.47:8089/qzcsbj/file/upload?name=" + shuju["name"] #接口文档中规定要将name的值跟在url之后,因此这里使用字典key提取name的值01拼接到url之后
res = RequestUtil().send_request(method="post", url=url, json=shuju, files=shuju1)
print(res.json())
'''
all.py
import pytest
if __name__ == '__main__':
pytest.main(['-vs'])
#其余文件不变
执行结果:
D:\JetBrains\PycharmProjects\API接口测试\venv\Scripts\python.exe D:\JetBrains\PycharmProjects\API接口测试\all.py
============================= test session starts =============================
testcases/test_api.py::TestRequest::test_token[args_name0] {'code': 2003, 'msg': '登录成功', 'data': {'token': 'ddance_3b660bd1-7c26-4791-b183-b4e07326884c'}}
PASSED
============================== 1 passed in 0.13s ==============================
Process finished with exit code 0
一个接口一般对应一个数据驱动yaml文件, 一个yaml可以以“-”为分隔符定义多组测试数据,@pytest.mark.parametrize可以根据有几组测试数据就把这个测试用例跑几遍
#演示
test_api-test_token.yaml
- #第1组数据
name: 01获取接口统一鉴权码token
request:
method: post
url: http://47.108.153.47:8089/qzcsbj/user/login
shuju:
username: ddance
password: '000000' #000000加上引号表示是字符串,防止被识别成int,int只有一位0
validate: None
- #第2组数据
name: 02获取接口统一鉴权码token
request:
method: post
url: http://47.108.153.47:8089/qzcsbj/user/login
shuju:
username: ddance
password: '000000' #000000加上引号表示是字符串,防止被识别成int,int只有一位0
validate: None
#其余文件不变
执行结果: #可以看到test_token测试用例被执行了2次,在实际使用时还可以将yaml中第2组数据写一个错误的值以验证测试用例准确性
D:\JetBrains\PycharmProjects\API接口测试\venv\Scripts\python.exe D:\JetBrains\PycharmProjects\API接口测试\all.py
============================= test session starts =============================
testcases/test_api.py::TestRequest::test_token[args_name0] {'code': 2003, 'msg': '登录成功', 'data': {'token': 'ddance_2d009055-3b49-4ca6-8183-f4ebcf7c27b0'}}
PASSED
testcases/test_api.py::TestRequest::test_token[args_name1] {'code': 2003, 'msg': '登录成功', 'data': {'token': 'ddance_ada0cffb-2a06-4eb5-9920-961446c3e65e'}}
PASSED
============================== 2 passed in 0.16s ==============================
Process finished with exit code 0
如果在加上pytest.ini配置文件一个简单的pytest测试框架就搭建完成了。
最后: 为了回馈铁杆粉丝们,我给大家整理了完整的软件测试视频学习教程,朋友们如果需要可以自行免费领取 【保证100%免费】
软件测试面试文档
我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。