点我免费领取全套软件测试(自动化测试)视频资料(备注“csdn000”)
在PC端登录公司的后台管理系统或在手机上登录某个APP时,经常会发现登录成功后,返回参数中会包含token,它的值为一段较长的字符串,而后续去请求的请求头中都需要带上这个token作为参数,否则就提示需要先登录。
这其实就是状态或会话保持的第三种方式token
。
token 由服务端产生,是客户端用于请求的身份令牌。
第一次登录成功时,服务端会生成一个包含用户信息的加密字符串token,返回给客户端并保存在本地,后续客户端只需要带上token进行请求即可,无需带上用户名密码。
token原理简单概括如下:
-
用户首次登录成功后,服务端会生成一个token值,服务端会将它保存保存在数据库中,同时也会将它返回给客户端;
-
客户端拿到token值后,保存在本地;
-
后续客户端再次发送除登录外的其他请求时,会把保存在本地的token值作为参数一起发送给服务端;
-
服务端收到客户端的请求后,会拿发送过来的token值与保存在数据库中的token值进行比较;
-
如果两个token值相同, 则说明当前用户处于登录状态;
-
如果数据库中没有这个token值或者token值已经生效,则需用户重新登录。
公司某管理后台系统,登录后返回token,接着去请求其他接口时请求头中都需要加上这个token,否则提示请先登录。
请求该系统的登录接口如下:
# @author: 给你一页白纸
import requests
import json
headers = {"Content-Type": "application/json;charset=utf8"}
url = "http://127.0.0.1:5000/login"
_data = {
"username": "刘德华",
"password": "123456"
}
res = requests.post(url=url, headers=headers, json=_data).text
print(res)
结果如下:
{
"code": 1000,
"msg": "登录成功!",
"token": "sh34ljjl08s32730djsh34ljjl08s32730djsh34ljjl08s32730djsh34ljjl08s32730djsh34ljjl08s32730djsh34ljjl08s32730dj"
}
在对扎样的项目做接口自动化测试时,需要先请求登录接口拿到token,再去请求别的接口。
每次请求其他接口时先请求一次登录接口,这样做虽然可行,但这样不仅会降低自动化的执行效率,而且每次都请求登录也会对服务器资源造成浪费。
这里介绍如下两种处理思路。
在执行用例之前,先请求登录接口,并将返回的token值存储在文件中(如yaml文件),后续请求需要用到token值则从该文件。
python中yaml文件的读写
最近在搭建自动化测试项目过程中经常遇到yaml文件的读写,为了方便后续使用,决定记下笔记。
YAML,Yet Another Markup Language的简写,通常用来编写项目配置,也可用于数据存储,相比conf等配置文件要更简洁。
-
支持的数据类型:
字典、列表、字符串、布尔值、整数、浮点数、Null、时间等
-
基本语法规则:
1、大小写敏感
2、使用缩进表示层级关系
3、相同层级的元素左侧对齐
4、键值对用冒号 “:” 结构表示,冒号与值之间需用空格分隔
5、数组前加有 “-” 符号,符号与值之间需用空格分隔
6、None值可用null 和 ~ 表示
7、多组数据之间使用3横杠---分割
8、# 表示注释,但不能在一段代码的行末尾加 #注释,否则会报错
注意:网上查找到各种博客都提到yaml缩进时不能使用tab键,但我在pycharm编辑器里实际使用时是可以使用tab键进行缩进的,读写时并没有报错!
python没有自带的处理yaml文件的库,需要下载第三方库PyYAML 或 ruamel.yaml ,这里我们安装PyYAML。
pip install pyyaml
# 下载速度慢的话加上清华镜像源
pip install pyyaml -i https://pypi.tuna.tsinghua.edu.cn/simple
四,读取yaml文件
1,从yaml中读取字典
yaml中的字典格式如下:
# yaml文件,文件名为yamlData
os: Android
osVersion: 10
account:
username: xiaoqq
password: 123456
deviceName: null
appPackage: ~
bool1: True
读取字典代码:
# @author: 给你一页白纸
import yaml
with open('./yamlData.yml', 'r', encoding='utf-8') as f:
result = yaml.load(f.read(), Loader=yaml.FullLoader)
print(result, type(result))
print(result['os'], type(result['os']))
print(result['osVersion'], type(result['osVersion']))
print(result['account'], type(result['account']))
print(result['account']['username'])
print(result['deviceName'])
print(result['appPackage'])
print(result['bool1'], type(result['bool1']))
读取结果:
{'os': 'Android', 'osVersion': 10, 'account': {'username': 'xiaoqq', 'password': 123456}, 'deviceName': None, 'appPackage': None}
Android
10
{'username': 'xiaoqq', 'password': 123456}
xiaoqq
None
None
True
从读取结果可以看出:
1,读取出来的数据不会改变原数据类型,即yaml里是什么数据类型,读出来就是什么类型。
2,Loader=yaml.FullLoader
参数不写的话对结果不会有影响,但运行时会出现警告信息。
3,yaml.load(f.read(), Loader=yaml.FullLoader)
也可以写成yaml.load(f, Loader=yaml.FullLoader)
,读取出来的结果相同。
yaml中list格式:数据前加'-' 并使用空格与数据间隔开,如下:
# yaml文件名yamlData
- Android
- 10
- null
- ~
- True
读取list代码:
# @author: 给你一页白纸
import yaml
with open('./yamlData.yml', 'r', encoding='utf-8') as f:
result = yaml.load(f.read(), Loader=yaml.FullLoader)
print(result, type(result))
读取结果:
['Android', 10, None, None, True]
3,从yaml中读取元组
yaml中存储元组格式:yaml中使用!!对数据类型进行转换,yaml中tuple由list转换而来。
如下:
# yaml文件名yamlData
!!python/tuple
- Android
- 10
- null
- ~
- True
读取元组代码:
# @author: 给你一页白纸
import yaml
with open('./yamlData.yml', 'r', encoding='utf-8') as f:
result = yaml.load(f.read(), Loader=yaml.FullLoader)
print(result, type(result))
读取结果:
('Android', 10, None, None, True)
在实际使用中,很多的时候往往是多种类型嵌套的数据。
如下yaml数据
# yaml文件名yamlData
os: Android
osVersion: 10
account:
- username1: xiaoqq
- password1: 123456
- username2: Lilei
- password2: 888888
deviceName: null
appPackage: ~
bool1: True
读取结果:
{'os': 'Android', 'osVersion': 10, 'account': [{'username1': 'xiaoqq'}, {'password1': 123456}, {'username2': 'Lilei'}, {'password2': 888888}], 'deviceName': None, 'appPackage': None, 'bool1': True}
4,从yaml中读取多组数据
yaml多组数据时,每组数据之间需要用3横杠分隔'---',如下:
os: Android
osVersion: 10
account1:
username1: xiaoqq
password1: 123456
---
os: ios
osVersion: 12
account1:
username2: Lilei
password2: 888888
从yaml中读取多组数据时需要使用yaml.load_all()
方法,返回结果为一个生成器,需要使用for循环语句获取每组数据。
代码如下:
# @author: 给你一页白纸
import yaml
with open('./yamlData.yml', 'r', encoding='utf-8') as f:
result = yaml.load_all(f.read(), Loader=yaml.FullLoader)
print(result, type(result))
for i in result:
print(i)
读取结果:
{'os': 'Android', 'osVersion': 10, 'account1': {'username1': 'xiaoqq', 'password1': 123456}}
{'os': 'ios', 'osVersion': 12, 'account1': {'username2': 'Lilei', 'password2': 888888}}
五,写入yaml文件
1,单组数据写入yaml文件
使用yaml.dump()方法,加入allow_unicode=True
参数防止写入的中文乱码,如下:
# @author: 给你一页白纸
import yaml
apiData = {
"page": 1,
"msg": "地址",
"data": [{
"id": 1,
"name": "学校"
}, {
"id": 2,
"name": "公寓"
}, {
"id": 3,
"name": "流动人口社区"
}],
}
with open('./writeYamlData.yml', 'w', encoding='utf-8') as f:
yaml.dump(data=apiData, stream=f, allow_unicode=True)
写入结果:
data:
- id: 1
name: 学校
- id: 2
name: 公寓
- id: 3
name: 流动人口社区
msg: 地址
page: 1
2,多组数据写入yaml文件
使用yaml.dump_all()方法,如下:
# @author: 给你一页白纸
import yaml
apiData1 = {
"page": 1,
"msg": "地址",
"data": [{
"id": 1,
"name": "学校"
}, {
"id": 2,
"name": "公寓"
}, {
"id": 3,
"name": "流动人口社区"
}],
}
apiData2 = {
"page": 2,
"msg": "地址",
"data": [{
"id": 1,
"name": "酒店"
}, {
"id": 2,
"name": "医院"
}, {
"id": 3,
"name": "养老院"
}],
}
with open('./writeYamlData.yml', 'w', encoding='utf-8') as f:
yaml.dump_all(documents=[apiData1, apiData2], stream=f, allow_unicode=True)
写入结果:
data:
- id: 1
name: 学校
- id: 2
name: 公寓
- id: 3
name: 流动人口社区
msg: 地址
page: 1
---
data:
- id: 1
name: 酒店
- id: 2
name: 医院
- id: 3
name: 养老院
msg: 地址
page: 2
在Python中除了PyYAML库之外,还有ruamel.yaml库也可以对yaml文件进行读写 *** 作,后续再记笔记进行介绍。
1,运行接口自动化测试框架,初始化时先请求登录接口,获取token值,并写入指定的yaml文件中。
# @author: 给你一页白纸
# 微信公众号:测试上分之路
import requests
import json
import yaml
def get_token():
'''
请求登录接口,获取token
:return:
'''
headers = {"Content-Type": "application/json;charset=utf8"}
url = "http://127.0.0.1:5000/login"
_data = {
"username": "刘德华",
"password": "123456"
}
res = requests.post(url=url, headers=headers, json=_data).text
res = json.loads(res)
token = res["token"]
return token
def write_yaml(token):
'''
写入yaml文件
:return:
'''
t_data = {
"token": token
}
with open("yaml文件路径", "w", encoding="utf-8") as f:
yaml.dump(data=t_data, stream=f, allow_unicode=True)
if __name__ == '__main__':
token = get_token() # 获取token
write_yaml(token) # 将token值写入yaml文件
2,执行测试用例时先读取yaml文件中token值,并将token加入headers中(也有些是将token放在请求参数中,视被测试项目具体情况而定),再发送请求。
点我免费领取全套软件测试(自动化测试)视频资料(备注“csdn000”)
# @author: 给你一页白纸
# 微信公众号:测试上分之路
import requests
import yaml
import pytest
import json
def read_yaml():
'''
读yaml文件
:return:
'''
with open('yaml文件路径', 'r', encoding='utf-8') as f:
result = yaml.load(f.read(), Loader=yaml.FullLoader)
token = result["token"]
return token
def test_check_user():
'''
查询个人信息(需要先登录系统)
:return:
'''
# 先从yaml文件中读取token
token = read_yaml()
# 再将token添加到请求头中
headers = {
"Content-Type": "application/json;charset=utf8",
"token": token
}
url = "http://127.0.0.1:5000/users/3"
res = requests.get(url=url, headers=headers).text
# 返回结果为json格式,转换为字典
res = json.loads(res)
# 断言code是否为1000
assert res["code"] == 1000
if __name__ == '__main__':
pytest.main()
这里仅仅只是举例说明,而在实际的框架中,我们需要把这些诸如yaml文件的读写这样的函数单独封装在某个模块中,供其他模块调用,这样会代码会更加清晰简洁。
利用pytest中的Fixture函数,作用域设置为session,并返回token值,后续测试方法/函数调用该Fixture函数。
pytest中Fixture的使用
什么是固件Fixture 翻译成中文即是固件的意思。
它其实就是一些函数,会在执行测试方法/测试函数之前(或之后)加载运行它们,常见的如接口用例在请求接口前数据库的初始连接,和请求之后关闭数据库的 *** 作。
我们之前在APP UI自动化系列中已经介绍过 unittest 的相关测试固件,如setup
、teardown
等。
而 pytest 中提供了功能更加丰富的Fixture
,用于实现setup
、teardown
功能。
使用@pytest.fixture()
进行定义,简单示例如下:
import pytest
@pytest.fixture()
def before():
print("连接数据库")
调用方式
调用单个fixture函数
-
方式一,使用fixture函数名作为参数
import pytest @pytest.fixture() def before(): print("连接数据库") # 调用before def test_01(before): print("执行test_01")
-
方式二,使用
@pytest.mark.usefixtures('fixture函数名')
装饰器import pytest @pytest.fixture() def before(): print("连接数据库") # 调用before @pytest.mark.usefixtures('before') def test_01(): print("执行test_01")
-
方式三,使用
autouse
参数自动执行fixture
函数import pytest # fixture函数定义的时候使用autouse参数,作用域范围内的测试用例会自动调用该fixture函数 @pytest.fixture(autouse=True) def before(): print("连接数据库") # 自动调用before def test_01(): print("执行test_01")
三种方式调用后的结果都如下:
我们可以看到,先执行了fixture
函数,再执行测试函数。
调用多个fixture函数
import pytest
@pytest.fixture()
def before():
print("连接数据库")
@pytest.fixture()
def before_s():
print("初始化数据")
def test_01(before, before_s):
print("执行test_01")
调用多个 fixture 函数时,由前至后依次执行,所以test_01()
调用时先执行before
,再执行before_s
。
对fixture函数重命名
定义fixture
函数时,可以利用name
参数进行重命名,方便用于调用,示例如下:
import pytest
@pytest.fixture(name='db')
def connect_order_db():
print("连接数据库")
def test_01(db):
print("执行test_01")
使用fixture传递测试数据
在执行完fixture
函数后,有时需要将该fixture
中得到到某些数据传递给测试函数/测试方法,用于后续的执行。
fixture
中提供普通传递和参数化传递两种数据传递方式。
普通传递
示例如下:
import pytest
@pytest.fixture()
def before():
print("连接数据库")
return "连接成功!"
def test_01(before):
print("执行test_01")
assert before == "连接成功!"
注意,如果自定义的fixture
函数有返回值,需要使用上面说的方式一调用才能获取fixture
函数的返回值并传入测试函数中,方式二就无法获取返回值。
参数化传递
对fixture
函数进行参数化时,需要使用参数params
,并且需要传入参数request
,简单示例如下:
import pytest
test_params = [1, 2, 0]
@pytest.fixture(params=test_params)
def before(request):
result = request.param
return result
def test_02(before):
print("执行test_02")
assert before
if __name__ == '__main__':
pytest.main()
执行结果:
可以看到,因为所调用的fixture函数进行了参数化,虽然只有一个测试函数但执行了3次。
上面我们举的例子都是把fixture函数放在测试用例模块里面,但如果很多测试模块需要引用同一个fixture函数怎么办,这是时候就需要把它放在命名为conftest
的模块里,这样同级或以下目录中的测试用例便能调用这些自定义的fixture函数。
例如,有如下目录:
├─testcase
│ │
│ ├─test_module_01
│ │ test_case_1.py
│ │ test_case_2.py
│ │
│ ├─test_module_02
│ │ test_case_3.py
test_module_01 中的test_case_1.py
与test_case_2.py
都需要调用同一个 fixture 函数,那么我们只需要在 test_module_01 中新建conftest.py
并编写这个fixture
函数即可,示例如下:
├─testcase
│ │
│ ├─test_module_01
│ │ conftest.py
│ │ test_case_1.py
│ │ test_case_2.py
│ │
│ ├─test_module_02
│ │ test_case_3.py
conftest.py
:
import pytest
@pytest.fixture(autouse=True)
def before():
print("连接数据库")
test_case_1.py
:
def test_01():
print("执行test_01")
test_case_2.py
:
def test_02():
print("执行test_02")
这样,执行这两个模块的测试用例时会自动先去调用conftest.py
中的before()
函数。
假设 test_module_02 中的 test_case_3.py 也需要调用这个before()
函数,那么这个时候我们就需要在上一层即 testcase 中新建conftest.py
并编写这个before()
函数,才能在 test_case_3.py 中调用,如下:
├─testcase
│ │ conftest.py
│ │
│ ├─test_module_01
│ │ conftest.py
│ │ test_case_1.py
│ │ test_case_2.py
│ │
│ ├─test_module_02
│ │ test_case_3.py
conftest.py
只作用于同级
或以下
目录中的测试模块,且需要注意,当以下
层级中存在了另一个conftest.py
,那么以下层级将由另一个conftest.py
文件接管。
pytest 的 fixture 作用域分session
、module
、class
、function
四个级别。
在定义 fixture 函数的时候通过scope
参数指定作用范围,默认为function
。
session
,每次会话执行一次module
,每个测试模块执行一次class
,每个测试类执行一次function
,每个测试方法执行一次
注意,对于单独定义的测试函数,class、function 都会起作用,可以从下列示例中看出来。
测试目录结构如下:
├─apiAutoTest
│ │ run.py
│ │
│ ├─testcase
│ │ │ conftest.py
│ │ │
│ │ ├─test_module_02
│ │ │ │ conftest.py
│ │ │ │ test_case_3.py
│ │ │ │ test_case_4.py
其中conftest.py
代码如下:
import pytest
@pytest.fixture(scope="session", autouse=True)
def session_fixture():
print("这是一个作用于session的fixture")
@pytest.fixture(scope="module", autouse=True)
def module_fixture():
print("这是一个作用于module的fixture")
@pytest.fixture(scope="class", autouse=True)
def class_fixture():
print("这是一个作用于class的fixture")
@pytest.fixture(scope="function", autouse=True)
def function_fixture():
print("这是一个作用于function的fixture")
test_case_3.py
代码如下:
import pytest
class TestOrder:
def test_a(self):
print("test_a")
def test_b(self):
print("test_b")
def test_c():
print("test_c")
test_case_4.py
代码如下:
def test_e():
print("test_e")
run.py
代码如下:
import pytest
if __name__ == '__main__':
pytest.main(["-s"])
运行run.py
,结果如下:
collected 4 items
testcase\test_module_02\test_case_3.py
这是一个作用于session的fixture
这是一个作用于module的fixture
这是一个作用于class的fixture
这是一个作用于function的fixture
test_a
.这是一个作用于function的fixture
test_b
.这是一个作用于class的fixture
这是一个作用于function的fixture
test_c
.
testcase\test_module_02\test_case_4.py
这是一个作用于module的fixture
这是一个作用于class的fixture
这是一个作用于function的fixture
test_e
.
============================== 4 passed in 0.04s ==============================
从结果可以看出来:
- 作用于
session
的fixture函数只在所有测试用例执行之前调用了一次 - 作用于
module
的fixture函数在每个测试模块执行之前调用了一次 - 作用于
class
的fixture函数在每个测试类执行之前调用了一次 - 作用于
function
的fixture函数在每个测试方法/测试函数执行之前调用了一次
注意,在定义的测试函数(如test_c()
、test_e()
)执行之前也会调用scope=class的fixture函数。
与 unittest 框架比较,pytest 中的Fixture
更加丰富,可扩展性更高。
Fixture
还有很多更加优雅的用法用于自动化测试项目中,本文只是以最简单的示例进行说明。
1,首先,在conftest中定义一个作用域为session的Fixture函数,用于请求登录接口返回token。
# @author: 给你一页白纸
# 微信公众号:测试上分之路
import pytest
import requests
import json
@pytest.fixture(scope="session")
def get_token_fixture():
'''
作用域为session的fixture函数,返回token
:return:
'''
headers = {"Content-Type": "application/json;charset=utf8"}
url = "http://127.0.0.1:5000/login"
_data = {
"username": "刘德华",
"password": "123456"
}
res = requests.post(url=url, headers=headers, json=_data).text
res = json.loads(res)
token = res["token"]
return token
2,接着,测试用例调用该Fixture。
def test_check_user(get_token_fixture):
'''
查询个人信息(需要先登录系统)
:return:
'''
# 通过Fixture函数g获取et_token_fixture值,即token,再将token添加到请求头中
headers = {
"Content-Type": "application/json;charset=utf8",
"token": get_token_fixture
}
url = "http://127.0.0.1:5000/users/3"
res = requests.get(url=url, headers=headers).text
res = json.loads(res)
print(res)
print(headers)
assert res["code"] == 1000
if __name__ == '__main__':
pytest.main()
执行测试用例结果如下:
说明思路二也是可行的,当然这里只执行了一条测试用例,如果执行很多的用例,效果会是怎样还没去验证,大家可以试试看。
三. 总结点我免费领取全套软件测试(自动化测试)视频资料(备注“csdn000”)
- 相对于Session/Cookies来说,请求量较大或者涉及第三方接口的系统,使用token更适合。
- 有些项目token是放在请求头中发送的,而有一些项目则是放在请求参数里发送的,做接口自动化时要明确是哪种方式。
- 接口自动化处理token时这两种思路可任选一种,如果使用pytest框架的话建议尝试思路二。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)