pytest自动化测试框架(基础篇)
pytest自动化测试框架(基础篇)
测试用例发现规则
1、模块名必须以test_开头或者_test结尾;
2、测试类必须以Test开头,并且不能带有init方法;
3、测试用例必须以test_开头;
print("hello python!")
pytest常用插件
pytest
pytest-html
pytest-xdist
pytest-rerunfailures
pytest-ordering
allure-pytest
pyyaml
requests
写入requrtments.txt文件中
pip install -r requrtments.txt
运行参数及其配置化
Pytest -h :获取帮助信息
Pytest -v:显示详细信息
Pytest -s:关闭输出捕获
Pytest -n X:使用X个进程并行化执行用例
Pytest -n auto:自动选进程数执行
Pytest --html=Path:生成HTML测试报告,并保持在Path路径
Pytest --self-contained-html:HTML文件
Pytest -reruns X:测试用例失败后,重试X次
Pytest 执行方式
1、命令行方式
2、main函数方式
import pytest
if __name__ == '__main__':
pytest.main()
3、pytest.ini执行方式
[pytest]
#参数
addopts = -sv --html=report/report.html
pytest前后置固件
setup_method():每个用例执行前执行一次
teardown_method():每个用例执行后执行一次
setup_class():每个类执行前执行一次
teardown_class():每个类执行后执行一次
使用fixture实现部分前后置
@pytest.fixture()
@pytest.fixture(scope="function", autouse=True)
def check_sql():
print("执行检查sql方法")
# 测试用例执行后执行
yield
print("关闭数据库")
1、scope:作用域:
function:在函数之前和之后执行;
--手动调用方式:在测试用例参数里面加上fixture的名称;
--fixture的返回值可以传递到测试用例中,通过fixture名称方式传递;
问题:测试类继承TestCase,测试用例调用fxiture执行报错
@pytest.fixture(scope="function")
def check_sql():
print("执行检查sql方法", end='\n')
yield
print("关闭数据库", end='\n')
class TestUi(TestCase):
def test_01(self, check_sql):
print("test_01执行中", end='\n')
def test_02(self,check_sql):
print("test_02执行中", end='\n')
def test_03(self):
print("test_03执行中", end='\n')

取消继承TestCase后,重新执行正常
class:在类之前和之后执行;
--手动调用方式:在类上面加上@pytest.mark.usefixtures(“fixture名称”)装饰器调用;
@pytest.fixture(scope="class")
def check_sql():
print("执行检查sql方法", end='\n')
yield True
print("关闭数据库", end='\n')
@pytest.mark.usefixtures("check_sql")
class TestUi:
def test_01(self):
print("test_01执行中", end='\n')
def test_02(self):
print("test_02执行中", end='\n')
def test_03(self):
print("test_03执行中", end='\n')
module:
package:
session:在整个项目会话之前和之后执行
2、autouse:自动调用fixture,默认为False
3、params:实现参数化
通过fixture函数的参数里面加入request来接收参数,然后通过request.param来取值;
@pytest.fixture(scope="function", name='cs', params=['data1','data2','data3'])
def check_sql(request):
print("执行检查sql方法", end='\n')
print(f"检查{request.param}")
yield True
print("关闭数据库", end='\n')
@pytest.mark.usefixtures("cs")
class TestUi:
def test_01(self):
print("test_01执行中", end='\n')
def test_02(self):
print("test_02执行中", end='\n')
def test_03(self):
print("test_03执行中", end='\n')
4、ids:不能单独使用,必须和params一起使用,作用是对参数起别名;
sql_data = ['data1', 'data2', 'data3']
@pytest.fixture(scope="function", name='cs', params=sql_data, ids=['1','2','3'])
def check_sql(request):
print("执行检查sql方法", end='\n')
print(f"检查{request.param}")
yield True
print("关闭数据库", end='\n')
@pytest.mark.usefixtures("cs")
class TestUi:
def test_01(self):
print("test_01执行中", end='\n')
def test_02(self):
print("test_02执行中", end='\n')
def test_03(self):
print("test_03执行中", end='\n')
执行结果:
test_case/test_ui.py::TestUi::test_01[1] 执行检查sql方法
检查data1
test_01执行中
PASSED关闭数据库
test_case/test_ui.py::TestUi::test_01[2] 执行检查sql方法
检查data2
test_01执行中
PASSED关闭数据库
test_case/test_ui.py::TestUi::test_01[3] 执行检查sql方法
检查data3
5、name:作用是给fixture起别名;一旦使用了别名,那么fixture的的名称就失效了,只能用别名;
fixture结合conftest.py文件使用
1、conftest.py专门用于存放fixture的配置文件,文件名是固定的;
2、在conftest.py文件里面所有的方法在调用时都不需要导包;
3、conftest.py文件可以有多个,并且多个conftest.py文件里面的多个fixture可以被一个测试用例使用;
setup_method,teardown_method,setup_class,teardown_class,fixture,conftest优先级
会话:fixture的session级别优先级最高;session级的fixture永远最先执行
如果fixture的autouse=True:
类:setup_class比class级别的fixture后执行;
函数:setup_method比function级别的fixture后执行;
如果fixture的autouse=Flase且使用@pytest.mark.usefixtures(“fixture名称”)装饰测试类:
类:setup_class比class级别的fixture先执行;
函数:function级别的fixture比setup_method先执行;
以上执行顺序是可以反转的(自学 不推荐反转)
官方建议fixture优先执行,理由如下:
Pytest 将 fixture 视为测试的“依赖项”,需要在测试方法(及其传统的 setup/teardown)之前准备好。
Fixture 可以跨测试类共享,而 setup_method 仅限于当前类。
实际应用建议:
1、优先使用fixture,现代 Pytest 推荐完全用 fixture 替代 setup_method
2、避免混用,混合使用 setup_method 和 fixture 可能导致执行顺序混乱,建议统一风格。
总结:pytest执行过程
1、查询当前目录下的conftest.py文件;
2、查询当前目录下的pytest.ini文件;
3、查询用例目录下的conftest.py文件;
4、查询测试用例的py文件中是否有setup,teardown,setup_class,teardown_class;
5、执行用例;
pytest断言
使用python自己的断言:assert
pytest结合allure-pytest插件生成美观的报告
1、安装allure-pytest插件;
pip install allure-pytest
2、下载allure,下载之后解压,解压之后再配置环境变量(把allure目录下的bin目录配置到系统变量path路径中);
allure压缩包下载路径:https://github.com/allure-framework/allure2/releases
解压软件包(建议直接放到Python文件夹下),然后添加bin目录到环境变量中
验证安装成功 CMD窗口输入:allure --version
PS C:\Users\10997> allure --version
2.33.0
注意:PC需要提前安装javaSDK

验证java环境安装成功:
PS C:\Users\10997> Java -version
java version "20.0.2" 2023-07-18
Java(TM) SE Runtime Environment (build 20.0.2+9-78)
Java HotSpot(TM) 64-Bit Server VM (build 20.0.2+9-78, mixed mode, sharing)
3、生成allure报告
先生成临时json报告,在pytest.ini文件中加入以下内容:
addopts = --alluredir=./temps --clean-alluredir
addopts = --alluredir=./temps --clean-alluredir
addopts = --alluredir=./tmeps --clean-alluredir
--alluredir=./temps:生成临时报告
--clean-alluredir:清空临时报告
生成allure正式报告:
if __name__ == '__main__':
pytest.main()
time.sleep(3)
os.system("allure generate ./temps -o ./reports --clean")
4、allure报告定制化(自学)
pytest之parametrize()实现数据驱动
@pytest.mark.parametrize(args_name,args_value)
@pytest.mark.parametrize(args_name, )
args_name:参数名称,用于将参数值传递给函数;
args_value:参数值;列表或者元组,有n个元素,用例将执行n次
第一种用法:
class TestUi(Suit01):
@pytest.mark.parametrize("data", ["1", "2", "3"])
def test_01(self, data):
print("test_01执行中", end='\n')
print(f"{data=}", end='\n')
执行结果如下:
testcases/test_ui.py::TestUi::test_01[1] setup_class 执行中
执行检查sql方法
setup_method 执行中
test_01执行中
data='1'
PASSEDteardown_method 执行中
关闭数据库
testcases/test_ui.py::TestUi::test_01[2] 执行检查sql方法
setup_method 执行中
test_01执行中
data='2'
PASSEDteardown_method 执行中
关闭数据库
testcases/test_ui.py::TestUi::test_01[3] 执行检查sql方法
setup_method 执行中
test_01执行中
data='3'
PASSEDteardown_method 执行中
关闭数据库
@pytest.mark.parametrize("data", (["1","2"], {"姓名":"黄浩"}, "3"))
第二种用法:
class TestUi(Suit01):
@pytest.mark.parametrize("data1,data2", (["香蕉", "牛奶"], ["苹果", "醋"], ["橘子", "水"]))
def test_01(self, data1, data2):
print("test_01执行中", end='\n')
print(f"{data1=}", end='\n')
print(f"{data2=}", end='\n')
执行结果如下:
testcases/test_ui.py::TestUi::test_01[\u9999\u8549-\u725b\u5976] setup_class 执行中
执行检查sql方法
setup_method 执行中
test_01执行中
data1='香蕉'
data2='牛奶'
PASSEDteardown_method 执行中
关闭数据库
testcases/test_ui.py::TestUi::test_01[\u82f9\u679c-\u918b] 执行检查sql方法
setup_method 执行中
test_01执行中
data1='苹果'
data2='醋'
PASSEDteardown_method 执行中
关闭数据库
testcases/test_ui.py::TestUi::test_01[\u6a58\u5b50-\u6c34] 执行检查sql方法
setup_method 执行中
test_01执行中
data1='橘子'
data2='水'
PASSEDteardown_method 执行中
关闭数据库
思考:与fixture的params区别
@pytest.fixture(scope="function", name='cs', params=sql_data, ids=['1', '2', '3'])
@pytest.mark.parametrize("data", ["1", "2", "3"])
YAML格式测试用例
1、yaml是一种数据格式,扩展名是yaml/yml,支持#注释,通过缩进表示层级,区分大小写;
用途:
-做配置文件
-编写自动化测试用例
2、数据组成:
map对象,健:(空格)值
name: 黄浩
数组,使用"-"表示列表
testcases:
-test_01.py
-test_02.py
-test_03.py
parametrize结合yaml实现接口自动化测试
读取yaml文件
-
name: 获取统一的接口鉴权码
request:
method: get
url: https://api.weixin.qq.com/cgi-bin/token
data:
grant_type: client_credential
appid: wx6b11b3efd1cdc290
secret: 106a9c6157c4db5f6029918738f9529d
validate: None
ROOT_DIR = Path(__file__).parent.parent
def read_yaml(yamlpath):
with open(str(ROOT_DIR) + '/' + yamlpath, mode='r', encoding='utf-8') as f:
value = yaml.load(stream=f, Loader=yaml.FullLoader)
return value
测试用例实现参数化
-
name: 获取统一的接口鉴权码1
request:
method: get
url: https://api.weixin.qq.com/cgi-bin/token
data:
grant_type: client_credential
appid: wx6b11b3efd1cdc290
secret: 106a9c6157c4db5f6029918738f9529d
validate: None
class TestApi(Suit01):
session = requests.Session()
access_token = ''
@pytest.mark.parametrize('caseinfo', read_yaml('testcases/get_token.yaml'))
def test_get_token(self, caseinfo):
print('test_get_token 执行中')
print(f"{caseinfo=}")
# name = caseinfo['name']
method = caseinfo['request']['method']
url = caseinfo['request']['url']
data = caseinfo['request']['data']
# validate = caseinfo['validate']
result = TestApi.session.request(method=method, url=url, params=data).json()
TestApi.access_token = result['access_token']
assert 'access_token' in result
-
name: 编辑标签接口1
request:
method: post
url: https://api.weixin.qq.com/cgi-bin/tags/update?access_token=
data: { "tag": { "id": 134,"name": "码尚教育66666" } }
validate: None
@pytest.mark.parametrize('caseinfo', read_yaml('testcases/adit_flag.yaml'))
def test_edit_flag(self, caseinfo):
print('test_edit_flag 执行中')
print(f"{caseinfo=}")
# name = caseinfo['name']
method = caseinfo['request']['method']
url = caseinfo['request']['url'] + TestApi.access_token
data = caseinfo['request']['data']
# validate = caseinfo['validate']
result = TestApi.session.request(method=method, url=url, json=data).json()
print(f"{result=}")
# result = {'errcode': 0, 'errmsg': 'ok'}
assert result['errcode'] == 0 and result['errmsg'] == 'ok'
若请求类型为:get,则request的data参数名称为params;若请求类型为:post,则request的data参数名称为json或者其它;
封装统一接口请求
创建requests_util.py文件
class RequestsUtil:
session = requests.Session()
def send_request(self, method, url, data, **kwargs):
result = ''
method = str(method).lower() # 设置字母全小写
if method == 'get':
result = RequestsUtil.session.request(method=method, url=url, params=data)
elif method == 'post':
result = RequestsUtil.session.request(method=method, url=url, json=data)
else:
pass
return result
封装接口自动化测试框架(大成)
框架一出,任何项目都能用
要解决的问题:
1、yaml文件如何实现接口关联封装;
2、yaml文件如何实现动态参数的处理;
3、yaml文件如何实现文件上传;
4、yaml文件如何实现断言,特别是有参数化的时候;
5、yaml文件数据量太大咋办;
6、接口自动化框架的扩展:加密接口,签名接口,自定义的功能接口;
接口测试
http协议

TCP3次握手建立可靠连接:
- 第一次握手
客户端向服务器发送一个 SYN(Synchronize Sequence Numbers)包,表示希望与服务器建立连接并随机生成初始序列号Seq = X。此阶段客户端处于 SYN_SENT 状态1。- 第二次握手
服务器接收到客户端的 SYN 包后,会返回一个 SYN+ACK 包作为应答。该包包含两个部分:一是确认序号Ack = X + 1表示已接收到来自客户端的第一个包;二是服务器也随机生成自己的初始序列号Seq = Y。此时服务器进入 SYN_RCVD 状态1。- 第三次握手
客户端再次发送 ACK 包给服务器,其中携带的是对服务器 SYN 包的确认信息Ack = Y + 1和自身的序列号Seq = X + 1。一旦服务器接收到这个 ACK 包,则表明双方均已准备好进行数据传输,整个连接正式建立,状态变为 ESTABLISHED1。
http请求方式
get:查询
post:新增
put:修改
delete:删除
http状态码
1XX
2XX
3XX
4XX
5XX
接口自动化(细节篇)
全面熟练requests库以及底层方法调用逻辑
requests请求部分:
def get(url, params=None, ** kwargs):
url:接口地址
params:参数,在get请求的ur1后面传的参数
** kwargs:可变长度的字典
def post(url, data=None, json=None, ** kwargs):
ur1:接口地址
data:参数(表单 --- x-www-form-urlencoded)
json:参数(JSON --- application/json)
Postman四种传参方式:
1.form-data(既有表单也有文件上传) files
2.x-www-form-urlencoded(纯表单) data
3.raw(json:application/json)(传json格式的参数)json
4.binary(application/octrent-stream):(二进制文件)data
** kwargs:可变长度的字典
def put(url, data=None, ** kwargs):
ur1:接口地址
data:参数
def delete(url, ** kwargs):
ur1:接口地址
def get(url, params=None, ** kwargs):
def post(url, data=None, json=None, ** kwargs):
def put(url, data=None, ** kwargs):
def delete(url, ** kwargs):
上面的四个方法其实底层都是调用的request方法
requests.request("get", url, params=params, ** kwargs)
data=None,
requests.request()调用的是session.request方法
session.request(method=method, url=url, ** kwargs)
def request(
self,
method, 请求方式
url, 请求路径
params=None, params参数
data=None,data参数
headers=None,请求头
cookies=None, cookiests信息
files=None,文件上传
auto=None,鉴权
timeout=None,
allow_redirects=True,
proxies=None,
hooks=None,
stream=None,
verify=None,
cert=None,
json=None,):
requests.request()和session.request()的区别:前者每个请求都是独立的,后者会自动关联所有请求的cookie信息;
requests响应部分:
res.text 返回字符串形式的结果
res.json() 返回字典形式的结果
res.content 返回字节类型的结果
res.status_code 返回状态码
res.reason 返回状态信息
res.cookies 返回cookie信息
res.encoding 返回编码格式
res.headers 返回响应头
res.request.xxx 得到请求信息
requests接口自动化测试实战
接口关联三个层次:
Ⅰ级–通过类变量保存中间变量,实现接口关联;
class EstApi:
access_token = ""
csrf_token = ""
# session = sessions.Session()
sess = requests.session()
# 获取access_token鉴权码接口
def test_get_token(self):
urls = "https://api.weixin.qq.com/cgi-bin/token"
datas = {
"grant_type": "client_credential",
"appid": "wx8a9de038e93f77ab",
"secret": "8326fc915928dee3165720c910effb86"
}
# res = requests.get(url=urls, params=datas)
res = EstApi.sess.request(method='get', url=urls, params=datas)
# 使用jsonpath() 提取access_token的值,返回列表
lst = jsonpath.jsonpath(res.json(), "$.access_token")
EstApi.access_token = lst[0]
# access_token = res.json()['access_token']
print(f"{lst=}")
# 查询标签接口
def test_select_flag(self):
urls = "https://api.weixin.qq.com/cgi-bin/tags/get"
datas = {
"access_token": EstApi.access_token
}
res = EstApi.sess.request(method='get', url=urls, params=datas)
print(f"{res.json()=}")
# 编辑标签接口
def test_edit_flag(self):
urls = "https://api.weixin.qq.com/cgi-bin/tags/update?access_token=" + EstApi.access_token
# datas1 = {
# "access_token": EstApi.access_token
# }
datas2 = {"tag": {"id": 134, "name": "A"}}
res = EstApi.sess.request(method='post', url=urls, json=datas2)
print(f"{res.json()=}")
# 文件上传接口
def test_file_upload(self):
urls = "https://api.weixin.qq.com/cgi-bin/media/uploadimg"
datas1 = {
"access_token": EstApi.access_token
}
datas2 = {
"media": open("C:\\Users\\10997\PycharmProjects\\api_pytest\\tupian.png", mode='rb')
}
res = EstApi.sess.request(method='post', url=urls, params=datas1, files=datas2)
print(f"{res.json()=}")
# 访问phpwind首页接口
def test_phpwind(self):
urls = "http://47.107.116.139/phpwind/"
res = EstApi.sess.request(method='get', url=urls)
# print(f"{res.text=}")
# 使用正则表达式匹配字符
EstApi.csrf_token = re.search('name="csrf_token" value="(.*?)"', res.text).group(1)
print(f"{EstApi.csrf_token=}")
# 登录接口
def test_login(self):
urls = "http://47.107.116.139/phpwind/index.php?m=u&c=login&a=dorun"
headers = {
"Accept": "application/json, text/javascript, /; q=0.01",
"X-Requested-With": "XMLHttpRequest"
}
datas = {
"username": "hh12345",
"password": "hh12345",
"csrf_token": EstApi.csrf_token,
"backurl": "http://47.107.116.139/phpwind/",
"invite": ""
}
res = EstApi.sess.request(method='post', url=urls, data=datas, headers=headers)
print(f"{res.json()=}")
Ⅱ级–通过单独的文件保存中间变量,实现接口关联;
创建requests.util.py文件
class RequestsUtil:
sess = requests.session()
def send_all_request(self, **kwargs):
return RequestsUtil.sess.request(**kwargs)
Ⅲ级–极限封装成零代码的方式实现接口关联;!!!
接口关联两种方式:
1、正则提取实现接口关联:
re.search() # 通过正则匹配一个值,通过下标【1】取值,没有找到返回None;
re.findall() # 通过正则匹配多个值,返回list,通过下标取值,没有找到返回None;
2、JsonPath提取实现接口关联:
jsonpath.jsonpath() # 返回一个列表,通过下标取值,没有找到返回None;
接口自动化测试框架的封装
1、去重冗余代码;
2、实现统一异常处理和日志监控;
引入用例管理框架pytest
作用:
1、发现用例:默认发现用例的规则
模块名必须以test_开头或者以*_test.py结尾;
测试类必须以Test开头;
测试方法必须以test_开头;
2、执行用例
3、判断结果
4、生成测试报告
pytest用例管理框架详细介绍
特点:
1、结合selenium,requests,appium实现web,接口,app自动化;
2、结合allure生成非常美观的报告以及和Jenkins实现持续集成;
3、拥有很多强大的插件:
pytest
pytest-html # 生成HTML报告
pytest-xdist # 并行执行用例
pytest-rerunfailures # 重复执行失败用例
pytest-ordering # 用例排序
pytest-base-url # 基础路径
allure-pytest # 生成allure测试报告
把这些放到requirements.txt里面,然后通过以下命令安装:
pip install -r requirements.txt
pytest用例管理框架执行方式
1、命令行运行
pytest
2、主函数运行
import pytest
if __name__ == '__main__':
pytest.main()
3、通过配置文件pytest.ini来改变以及执行用例;
[pytest]
# 配置参数
addopts = -vs -m "smoke or user"
# 改变用例的查找规则
testpaths = ./testcases1 ./testcases2
# 改变模块的查找规则
python_files = test_*.py
# 改变类的查找规则
python_classes = Test*
# 改变函数的查找规则
python_functions = test_*
# 标记
markers =
smoke:冒烟用例
user:用户管理用例
不管是命令行还是主函数都会读取pytest.ini文件来执行
pytest用例管理框架的前后置(固件/夹具)
def setup():
print('每个用例之前执行一次')
def teardown():
print('每个用例之后执行一次')
def setup_class():
print('每个类之前执行一次')
def teardown_class():
print('每个类之后执行一次')
更强大的前后置:fixture
装饰器:
@pytest.fixture(scope="作用域",params="参数化",autouse="自动执行",idl="参数别名",name="别名")
scope: function class module package session
params: 参数化["mysql","redis"] 需要使用request参数和yield/return request.param
autouse: True和False
idl: 参数别名
name: 夹具别名:用了夹具别名,夹具原名就会失效
@pytest.fixture(scope="function", autouse=True, params=['mysql','redis'], ids=["m","r"])
def check_data(request):
print("打开数据库")
yield request.param
print("关闭数据库")
pytest接口关联封装(基于一个独立的文件)
创建yaml_util.py文件
# 写入
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)
# 读取
def read_yaml(key):
with open(os.getcwd() + '/extract.yaml', mode='r', encoding='utf-8') as f:
value = yaml.load(stream=f, Loader=yaml.FullLoader)
return value[key]
# 清空
def clear_yaml():
with open(os.getcwd() + '/extract.yaml', mode='w', encoding='utf-8') as f:
# 清空文件
f.truncate()
YAML详细介绍
键值对 键:(空格)值
列表 -
yaml用例格式:
-
feature: 模块
story: 接口
title: 用例标题
request:
method: 请求方式
url: 请求路径
headers: 请求头
data: 请求参数
vilidate: 断言
数据驱动
@pytest.mark.paramtrize(“参数名”,“值”)
创建test_api.yaml文件
-
feature: test_api
story: get_token
title: 获取正确的鉴权码
request:
method: get
url: "https://api.weixin.qq.com/cgi-bin/token"
headers: null
params:
"grant_type": "client_credential"
"appid": "wx8a9de038e93f77ab"
"secret": "8326fc915928dee3165720c910effb86"
vilidate: null
修改测试用例为:
@pytest.mark.parametrize("testinfo", read_yaml_testcase('testcases/test_api.yaml'))
def test_get_token(self, testinfo):
methods = testinfo['request']['method']
urls = testinfo['request']['url']
datas = testinfo['request']['params']
res = RequestsUtil().send_all_request(method=methods, url=urls, params=datas)
# 使用jsonpath() 提取access_token的值,返回列表
lst = jsonpath.jsonpath(res.json(), "$.access_token")
TestApi.access_token = lst[0]
# 将access_token值保存到extract.yaml文件中
write_yaml({"access_token": lst[0]})
class TestApi:
# 获取access_token鉴权码接口
@pytest.mark.parametrize("testinfo", read_yaml_testcase('testcases/test_get_token.yaml'))
def test_get_token(self, testinfo):
methods = testinfo['request']['method']
urls = testinfo['request']['url']
datas = testinfo['request']['params']
res = RequestsUtil().send_all_request(method=methods, url=urls, params=datas)
print(res.json())
# 使用jsonpath() 提取access_token的值,返回列表
lst = jsonpath.jsonpath(res.json(), "$.access_token")
TestApi.access_token = lst[0]
# 将access_token值保存到extract.yaml文件中
write_yaml({"access_token": lst[0]})
# 编辑标签接口
@pytest.mark.parametrize("testinfo", read_yaml_testcase('testcases/test_edit_flag.yaml'))
def test_edit_flag(self, testinfo):
methods = testinfo['request']['method']
urls = testinfo['request']['url'] + read_yaml("access_token")
datas2 = testinfo['request']['json']
res = RequestsUtil().send_all_request(method=methods, url=urls, json=datas2)
print(f"{res.json()=}")
# 查询标签接口
@pytest.mark.parametrize("testinfo", read_yaml_testcase('testcases/test_select_flag.yaml'))
def test_select_flag(self, testinfo):
methods = testinfo['request']['method']
urls = testinfo['request']['url']
datas = testinfo['request']['params']
datas['access_token'] = read_yaml('access_token')
res = RequestsUtil().send_all_request(method=methods, url=urls, params=datas)
print(f"{res.json()=}")
# 文件上传接口
@pytest.mark.parametrize("testinfo", read_yaml_testcase('testcases/test_file_upload.yaml'))
def test_file_upload(self, testinfo):
methods = testinfo['request']['method']
urls = testinfo['request']['url']
datas1 = testinfo['request']['params']
datas1['access_token'] = read_yaml('access_token')
datas2 = testinfo['request']['files']
datas2['media'] = open(datas2['media'], mode='rb')
res = RequestsUtil().send_all_request(method=methods, url=urls, params=datas1, files=datas2)
print(f"{res.json()=}")
问题思考:
1、如何在yaml中调用随机数方法;
2、一个接口对应一个yaml,yaml数据多了怎么处置;
3、yaml中如何断言;
allure报告
1、下载allure,配置allure环境变量
2、生成临时json报告
addopts = -vs --alluredir=./temps --clean-alluredir
3、生成allure测试报告
os.system("allure generate ./temps -o ./reports --clean")
web自动化(细节篇)
自动化测试的误区
1、自动化测试找不到bug
2、完全用不到自动化测试
3、有了自动化测试,就不用手工测试了
简易自动化价值或收益(ROI)
ROI = (手工测试成本*迭代次数)— 第一次自动化成本 —(自动化脚本维护*迭代成本)
适合做自动化测试的项目
适合项目:
1、产品型项目:功能是增量开发,回归测试的用例量大,存在大量重复的用例
不适合项目:
1、定制版项目:项目周期短,业务很复杂,项目需求不稳定,涉及物理交互
自动化测试的介入点
项目的中后期或者维护期;
自动化覆盖率(自动化测试用例和手工测试用例的占比)
RTN分组自动化覆盖率:87%
自动化实施的过程
1、可行性分析
2、框架选择(selenium,RF)
3、需求分析
4、制定计划
5、测试用例的设计
6、环境搭建,框架搭建
7、版本控制,持续集成,无人值守,报告生成
8、脚本维护
python+selenium环境搭建
待补齐
元素定位
元素定位失败分析:
1、元素是否加载完成?
2、页面是否存在frame和iframe?
3、元素是否不可用,不可读,不可见?
4、是否是动态元素,动态DIV层,自定义控件?
前提:元素必须唯一
八种定位方式:
id
driver.find_element(By.ID, "kw").send_keys("西昌学院")
name
driver.find_element(By.NAME, "wd").send_keys("四川大学")
xpath
# 绝对路径 用一个 /开头
driver.find_element(By.XPATH, "/html/body/div/div[2]/div[5]/div[1]/div/form/span[1]/input").send_keys("西昌学院")
# 相对路径 用 //开头
driver.find_element(By.XPATH, "//span/input").send_keys("西昌学院")
# 相对路径 + 索引
driver.find_element(By.XPATH, "//span[1]/input").send_keys("西昌学院")
# 相对路径 + 属性
# <input type="text" class="s_ipt" name="wd" id="kw" maxlength="100" autocomplete="off" placeholder="印度男子不慎摔入余烬堆身亡">
driver.find_element(By.XPATH, "//input[@class='s_ipt']").send_keys("西昌学院")
driver.find_element(By.XPATH, '//input[@autocomplete="off"]').send_keys("西昌学院")
# 相对路径 + 部分属性
# 属性值的开头
driver.find_element(By.XPATH, '//input[starts-with(@class, "s")]').send_keys("西昌学院")
# 属性值的结尾
driver.find_element(By.XPATH, '//input[substring(@class,3)="ipt"]').send_keys("西昌学院")
# 属性值的包含
driver.find_element(By.XPATH, '//input[contains(@autocomplete,"ff")]').send_keys("西昌学院")
# 相对路径 + 通配符
driver.find_element(By.XPATH,'//*[@autocomplete="off"]').send_keys("西昌学院")
driver.find_element(By.XPATH,'//*[@*="off"]').send_keys("西昌学院")
# 相对路径 + 文本 <span class="soutu-hover-tip" style="display: none;">按图片搜索</span>
driver.find_element(By.XPATH, '//span[text()="按图片搜索"]').get_attribute('class')
css 与xpath相似
略
link_text
driver.find_element(By.LINK_TEXT, "新闻").click()
partial_link_text
driver.find_element(By.PARTIAL_LINK_TEXT, "hao").click()
class_name
基本不用
tag_name
基本不用
项目实战
# frame和iframe(框架)
# 进入框架
driver.switch_to.frame("框架名")
# 跳出框架
driver.switch_to.default_content()
# 处理下拉框
sel = Select(driver.find_element())
sel.select_by_index() # 下标
sel.select_by_value() # 值
sel.select_by_visible_text() # 文本
设计模式
1、PO:page object model 页面对象模型
# 基础层 base 主要放selenium原生的方法;
from selenium import webdriver
from selenium.webdriver.support.select import Select
class BasePage:
def __init__(self, driver):
self.driver = driver
# self.driver = webdriver.Edge()
# self.driver.implicitly_wait(5)
# def open_browser(self):
# self.driver = webdriver.Edge()
# # 智能等待5min
# self.driver.implicitly_wait(5)
# 打开网页
def get(self, url):
self.driver.get(url)
# 定位元素
def location_element(self, args: tuple):
"""传入元组数据,然后解包传给find_element"""
return self.driver.find_element(*args)
# 发送value给元素
def send_keys(self, args, value):
self.location_element(args).send_keys(value)
# 单击元素
def click(self, args):
self.location_element(args).click()
# 进入框架
def goto_frame(self, frame_name):
self.driver.switch_to.frame(frame_name)
# 退出框架
def out_frame(self):
self.driver.switch_to.default_content()
# 下拉框选择
def choose_select(self, args, value):
sel = Select(self.location_element(args))
sel.select_by_value(value)
# 页面对象层 pageobject 继承基础层 主要放页面的元素和页面的动作 一个页面就是一个类
from selenium.webdriver.common.by import By
from base.base_page import BasePage
class LoginPage(BasePage):
# 封装页面元素
current_url = "http://localhost/signin"
user_loc = (By.ID, "email")
password_loc = (By.ID, "password")
login_button_loc = (By.XPATH, '//button[text()="登录"]')
# 封装页面动作
def login_dify(self, username="1099706290@qq.com", password="@huang213"):
# self.open_browser()
self.get(self.current_url)
self.send_keys(self.user_loc, username)
self.send_keys(self.password_loc, password)
self.click(self.login_button_loc)
# 测试用例层 testcase 调用pageabject 存放一个测试用例以及测试数据
class TestDify:
driver = webdriver.Edge()
driver.implicitly_wait(5)
username = ""
password = ""
def test_login(self):
lp = LoginPage(self.driver)
lp.login_dify()
sleep(2)
2、关键字驱动
略
项目实战
略
引入pytest
unittest测试框架(耦合)
1、新建一个类继承unittest.TestCase类
2、导入unittest
3、新建一个以test_开头的测试用例
pytest测试框架(非耦合)
1、模块名必须以test_开头或者_test结尾
2、测试类名必须以Test开头
3、测试函数必须以test_开头
conftest.py
import pytest
from selenium import webdriver
# 打开浏览器,一个测试类只执行一次
@pytest.fixture(scope="class", autouse=True)
def open_browser():
print("打开浏览器")
driver = webdriver.Edge()
driver.implicitly_wait(5)
yield driver
print("关闭浏览器")
test_dify.py
class TestDify:
username = ""
password = ""
def test_login(self, open_browser):
lp = LoginPage(open_browser)
lp.login_dify()
sleep(2)
def test_tansuo(self, open_browser):
lp = LoginPage(open_browser)
lp.login_dify()
ts = TanSuo(open_browser)
ts.tansuo()
sleep(2)
生成企业级的allure报告
1、下载allure,并配置环境文件
2、下载allure-pytest插件
3、生成临时文件
opts = -vs --alluredir=./temps --clean-allure
4、生成allure报告
os.system("allure generate ./temps -o ./reports --clean")
报告定制:
定制错误截图示例:
def test_login(self, open_browser):
try:
lp = LoginPage(open_browser)
lp.login_dify()
sleep(2)
assert 1==2
except Exception as e:
# 保存错误截图
file_path = "C:/Users/10997/PycharmProjects/WebPytest/error_log/login_error.png"
open_browser.save_screenshot(file_path)
# 把错误截图放到allure报告中
with open(file_path, mode="rb") as f:
allure.attach(body=f.read(), name="login_error.png", attachment_type=allure.attachment_type.PNG)
pytest实现数据驱动
pytest.mark.parametrize(args_name, args_value)
args_name:参数名
args_value:参数值(一般为list tuple 字典列表,字典元组等),列表里有多少个元素,用例就执行多少次
数据类型为excel,读取插件:xlrd openpyxl
excel_util.py
def read_excel(excel_url, sheet_name):
# 加载工作簿
wb = openpyxl.load_workbook(excel_url)
# 获取sheet页对象
sheet = wb[sheet_name]
# 打印sheet最大的行数和列数
print(sheet.max_row, sheet.max_column)
# 读取excel数据,组成列表数据
row_list = []
for row in range(2, sheet.max_row + 1):
col_list = []
# print(sheet.cell(row, 5).value)
if sheet.cell(row, 5).value == 'y':
for col in range(1, sheet.max_column):
col_list.append(sheet.cell(row, col).value)
# print(col_list)
row_list.append(col_list)
return row_list
test_dify.py
class TestDify:
@pytest.mark.parametrize("testinfo", read_excel(excel_url="./testcases/test_dify.xlsx", sheet_name="test_login"))
def test_login(self, open_browser, testinfo):
username = testinfo[2]
password = testinfo[3]
try:
lp = LoginPage(open_browser)
lp.login_dify(username, password)
sleep(2)
# assert 1==2
except Exception as e:
# 保存错误截图
file_path = "C:/Users/10997/PycharmProjects/WebPytest/error_log/login_error.png"
open_browser.save_screenshot(file_path)
# 把错误截图放到allure报告中
with open(file_path, mode="rb") as f:
allure.attach(body=f.read(), name="login_error.png", attachment_type=allure.attachment_type.PNG)
jenkins持续集成及集成allure报告
1、下载allure插件(allure-jenkins-plugin)并且放到jinkins的plugins目录下
2、在jinkens中配置allure
3、在job中加入allure报告
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)