pytest测试框架
单元测试框架定义:针对软件开发最小的单元(函数,方法)进行正确性位置测试
单元测试框架:java(junit,testing)python(unittest,pytest)
单元测试框架的作用
- 测试发现:从多个文件中找到测试用例
- 测试执行:按照一定的顺序和规则去执行并且生成结果
- 测试判断:通过断言判断预期结果和实际结果的差异
- 测试报告:统计测试的结果,耗时,通过率等生成美观的测试报告
自动化框架的内容:单元测试框架,pom设计模式,数据驱动,关键字驱动,全局配置文件的封装,日志监控,selenium和request二次封装,断言,报告邮件,更多…
pytest简介和安装
- pytest比unittest更灵活,容易上手
- pytest可以和selenium,reques,appnium结合实现web自动化,接口自动化,app自动化
- pytest可以实现测试用例的跳过和reruns失败用例重试
- pytest可以和allure生成测试报告
- pytest可以和jenkins持续集成
- pytest提供强大的插件
如下:建立requirements.txt 内容如下
pytest 本身
pytest-html 生成html格式化报告
pytest-xdist 测试用例分布式进行,多CPU分发
pytest-ordering 用于改变测试用例的执行顺序
pytest-rerunfailures 用例失败后重跑
allure-pytest 用于生成美观的测试报告
终端上输入安装:
pip install -r requirements.txt
检验:
pytest --version
模块命名
- 以test_开头或者 _test结尾
- 测试类必须以Test开头并且不再有ini方法
- 测试方法必须以test开头
测试用例的运行方式
1.主函数运行方式
(1)运行所有:pytest.main()
class TestLogin:def test_01_login(self):print("测试登录")if __name__ == '__main__':pytest.main()
(2)运行指定模块:
pytest.main(['-vs','test_login.py'])
(3)指定目录:
pytest.main(['-vs','./test_other.py'])
(4)通过nodeid指定用例运行:nodeid由模块名,分隔符,类名,函数名组成
pytest.main([’-vs‘,’./interface_testcase/test_interface.py::test_04_func‘])
2.命令行模式
(1)运行所有:
pytest
(2)指定模块:
pytest -vs test_login.py
(3)指定目录:
pytest -vs ./test_other.py
(4)指定目录:
pytest -vs ./interface_testcase/test_interface.py::test_04_func
3.参数详解
-v 输出更详细的信息
-s 输出调试信息
-vs:这两个参数一起用
-n:支持多线程或者分布式运行测试用例
如:pytest -vs ./test_login.py -n 2
–returns NUM:失败用例重跑
-x:表示只要要一个用例报错,那么测试就停止
–maxfall=2 出现两个用例失败就停止
-k:根据测试用例的部分字符串指定测试用例
如:pytest -vs/testcase -k"ao"
class TestLogin:def test_01_login(self):print("测试登录")if __name__ == '__main__':pytest.main(['-s'])
案例:创建一个all.py文件运行指定的test用例和控制输出的信息
import pytestif __name__ == '__main__':pytest.main(['-vs','test_login.py'])
相当于终端输入
pytest -vs test_login.py
3.结合pytest.ini全局配置文件执行
pytest为整个pytest的核心配置文件
-
位置:一般放在项目的根目录
-
编码:必须是ANSI编码格式,可以使用notepad++修改编码格式
-
作用:改变pytest默认行为
-
运行规则:不管是主函数的模式运行,命令行模式运行,都会去读取ini文件
1 [pytest] 2 #命令行参数 3 #常见:--html=./reports/report.html --reruns 2 4 addopts = -vs -m "user_manager or smoke" 5 #配置执行的用例位置 6 testpaths = ./testcases 7 #配置修改默认的模块规则 8 python_files = test_*.py 9 #配置修改默认的类规则 10 python_classes = Test* 11 #配置修改默认的用例规则 12 python_functions = test_* 13 #配置基础路径 14 base_url = http://www.baidu.com 15 #标记 16 markers = 17 smoke:冒烟测试用例 18 product_manage:商品管理 19 user_manager:用户管理
pytest 测试用例的顺序
区分
unittest:ascII的大小来绝对的执行顺序
pytest:默认从上到下
改变默认的执行顺序,使用mark标记
添加注释,其中1是执行的顺序
@pytest.mark.run(order=1)
如何分组执行(冒烟、分模块执行、分接口和web执行)
smoke:冒烟用例,分布在各个模块内
添加冒烟标记在.py文件
@pytest.mark.smoke
注意要在pytest.ini文件打一下标记
#标记markers =
smoke:冒烟测试用例
product_manage:商品管理
user_manager:用户管理
在终端输入
pytest -vs -m "smoke"
想执行其他模块
pytest -vs -m "smoke and 其他模块的名称"
跳过执行的用例
-
无条件跳过
@pytest.mark,skipif(reason=’跳过原因‘)
-
有条件跳过
@pytest.mark,skipif(条件,reason=’跳过原因‘)
例如
@pytest.mark.skipif(age>=18,reason='已成年')
pytest框架的一些前后置(固件,夹具)处理,常用的三种
前后置:setup_class、setup、teardown、teardown_class
class TestPosition:def setUp_class(self):print('\n 在每个类执行前的准备工作,例如:创建日志对象、数据库链接、接口请求对象')def setup(self):print('\n 执行用例之前的开始动作,比如打开浏览器')def test_01(self):print('\n 测试用例')def teardown(self):print('\n 执行用例之前的扫尾动作,比如关闭浏览器')def teardown_class(self):print('\n 在每个类执行前的准备工作,例如:销毁日志对象、数据库链接、接口请求对象')
使用@pytest.fixture()装饰器实现部分用例的前后置
装饰器:
@pytest.fixtrue(scope=“作用范围”,autouse=“自动执行”,params=“参数化”,ids=“参数别
名”,name=“固件别名”)
案例:
@pytest.fixture(scope="",params="", autouse="",ids="",name="")
def my_fixture():print('这是前置的方法,可以实现部分以及全部用例的前置')yieldprint('这是后置方法,可以实现部分以及全部用例的后置')
def test_02(self,my_fixture):print('\n 执行fixture')
用法:
- scope表示的是被@pytest.fixture标记的方法的作用域。function(默认),class,moudle,package/session
- params:参数化(支持,列表[],元组(),字典列表[{},{},{}],字典元组({},{},{})
import pytest@pytest.fixture(scope='function',params=['参数1','参数2','参数3'])
def my_fixture(request):print('前置')yield request.param # 注意yield和return都表示返回的以上,但是return后面不能跟代码,yield可以print('后置')
def test_02(self,my_fixture):print('\n 执行fixture')
注意:params和yield不能一起用但是可以和前置一起用。
-
autouse=True:自动化使用,默认False
-
ids:当使用params参数化时,给每一个值设置一个变量名,意义不大
-
name:给表示的是被@pytest.fixture标记的方法取一个别名
当取了别名之后,那么原来的名就用不了了
通过conftest.py和@pytest.fixture()结合使用实现全局的前置应用(如:项目的全局登录,模块的全局处理)
-
conftest.py文件是单独存放的一个夹具的配置文件,名称不能更改
-
用处可以在不同的py文件中使用同一个fixture函数
-
原则上conftest.py需要和运行的用例放在统一层,并且不需要做任何的import导入的操作
案例
全局拥有全局前置,各个子模块下面有一个前置,对于用例编写为
def test_01_testconftest(self,all_fixtuture,user_fixture)print("test用例")print(user_fixture)print(all_fixture)
这样全局前置包含了子模块的前置
setup/teardown,setup_class/teardown_class 它是作用于所有用例或者所有的类
@pytest.fixture() 它的作用是局部的也是全局的
conftest.py和@pytest_fixture()结合使用,作用于全局的前后置
断言
assert
pytest结合allure+pytest插件生成allure测试报告
以前是使用的是pytest_html生成,现在可以使用allure+pytest结合生成美观的报告
1.下载、解压、配置path路径
https://github.com/allure-framework/allure2/releases
配置环境变量如:
D:\allure-2.30.0\bin
验证
allure --version
2.加入命令到pytest.ini文件,生成json格式的临时报告
addopts = -vs --alluredir ./temp
3.生成allure报告
allure generate 命令,固定的
./temp 临时的json格式的报告
-o 输出output
./report 生成allure报告路径
.clear 清空./report路径原来的报告
if __name__ == '__main__':time.sleep(3)# pytest.main(['-vs','test_login.py'])pytest.main()# allure generate 命令,固定的;./temp 临时的json格式的报告 -o 输出output ./report 生成allure报告路径 .clear 清空./report路径原来的报告os.system('allure generate ./temp -o ./report --clear')
yaml接口自动化实战
1,断言的封装
2,allure报告的定制
3,关键字驱动和数据驱动结合实现接口自动化测试
4,python的反射
正常:先初始化对象,再调方法
反射:通过对象得到类对象,然后通过类对象调用方法
5,jenkins的持续集成和allure报告的集成,并且根据项目的自动化的报告错误率发送电子邮件
pytest接口自动化-基础篇
接口测试
发展背景:
目前市面上主流的接口测试工具:(适用于中小型项目)
- postman+newman+git+jenkins
- jmeter+ant+git+jenkins
目前主流的接口自动化测试技术:(适用于大小型的项目)
requests库
安装
requests用来发送http请求以及接受http响应的python第三方库,主要用于接口自动化测试
pip install requests
requests库常用的方法
requests.get() url是接口的地址,param用于传参
requests.post() url是接口地址,data用于传参,json也适用传参
data和json传参的区别:主要是通过请求头Content-Type来区分
Content-Type:作用是服务器要求传入的报文的内容类型
请求:请求方式,请求路径,请求头,请求正文
requests.pull()
requests.delete()
requests.request() 可以发送所有类型的请求:get,post,put,delete
拓展:postman的四种传参的方式对应的Content-Type的值如下:
from-data:Content-Type:multipart/form-data、boundary=
x-www-from-urlencoded:Content-Type:applic.ation/x-www-form-urlencoded
raw:
text:Content-Type:text/plain
javascript:Content-Type:application/javascript
json:Content-Type:application/json
html:Content-Type:application/xml
binary:Content-Type:application/binary
data和json传参以及Content-Type的关系如下:
1.data传参:
data传参:报文时dict类型,那么默认Content-Type application/x-www-form-urlencoded
data传参:报文是str类型,那么默认Content-Type:text/plain
2.json传参:
报文可以是dict类型,那么默认Content-Type application/json
注意:
-
json是字典类型的字符串显示。
-
json.loads() 把json字符串转化为dict格式,也可以传str格式(如果是嵌套字典那么就需要使用json.dumps()把嵌套字典转化为json字符串)
json.dumps() 把dict格式转换为json字符串(包括嵌套的dict)
不管是get,post,put,delete,都是调用的requests.request方法。而requests.reques方法调用的是session.reques方法
method 请求方式
url 请求路径
params=None get方式传参
data=None post方式传参
json=None post方式传参
headers=None 请求头
cookie=None 请求cookie
files=None 文件上传
requests模块返回的response对象详解
- res.json() 获取返回的字典格式的数据
- res.text 获得返回的字符串格式的数据
- res.content 获得返回的bytes字节类型的数据
- res_status_code 返回状态码
- res.reason 返回状态信息
- res.cookies 返回cookie信息
- res.headers 返回响应头
- res.request.xxx 返回需求的数据,如:需求头,请求参数
请求必须带有请求头的接口,以及需要cookie鉴权和session鉴权的接口
<input type="hidden" name="csrf_token" value="e7202bf558a7eb34"/>
90%以上的基于web的接口都有cookie鉴权
两种解决方式:
-
使用cookie关联
案例注释:接口关联变量,在第一个接口取值,在第二个接口传值,第三个接口文件上传,访问首页的接口,解决鉴权
import requestsclass TestRequest:#全局变量,类变量,通过类名调用access_token=""csrf_token=""php_cookie=""def test_get_token(self):url:"https://api.weixin.qq.com/cgi-bin/token"data:{"grant_type":"client_credential","appid":"wx74a8627810cfa300","secret":"e40a02f9cf9d79df497e6aaf93ab80"}res=requests.request(method="get",url=url,params=data)print(res.json())TestRequest access_token = res.json()['access_token']# post请求:编辑标签接口def test_edit_flag(self):url:"https://api.weixin.qq.com/cgi-bin/tags/updat?access token="+TestRequest.access_tokendata=["tag":{"id":134,"name":"广东人"}]str_data=json.dumps(data)res=requests.request(method="post",url=url,data=str_data)print(res.json())# 文件上传 def test_file_upload(self):url:"https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token="+TestRequest.access_token"+TestRequest.access_tokendata={"media":open(r"E:\shu.png","rb")}res=requests.request(method="post",url=url,files=data)print(res.json())# 访问首页的接口 def test_start(self):url:"https://47.107.116.139/phpwind"res=requests.request(method="get",url=url)print(res.text) # 正则提取token obj=re.search('name="csrf_token" value="(.*?)"',res.text) # print(obj.group(1)) TestRequest.csrf_token=obj.group(1) # 解决鉴权的方式一:提取cookie TestRequest.php_cookie=res.cookies# 登录接口 def test_login(self):url:"https://47.107.116.139/phpwind/index.php?m=u&c=login&a=dorun"data={"username":"msxy","password":"msxy","csrf_token":TestRequest.csrf_token,"backurl":"http://47.107.116.139/phpwind/","invite":""}# 请求头headers={"Accept":"application/json, text/javascript, /; q=0.01","X-Requested-with":"XMLHttpRequest"}res=requests.request(method="post",url=url,data=data,headers=headers,cookies=TestRequest.php_cookie)print(res.json()) # 解决鉴权的方式一:提取cookie TestRequest.php_cookie=res.cookiesif __name__ == '__main__':TestRequest().test_get_token()TestRequest().test_edit_flag()TestRequest().test_file_upload()TestRequest().test_start()TestRequest().test_login()
-
使用session关联
import requestsclass TestRequest:#全局变量,类变量,通过类名调用access_token=""csrf_token=""# php_cookie=""sess=requests.session()def test_get_token(self):url:"https://api.weixin.qq.com/cgi-bin/token"data:{"grant_type":"client_credential","appid":"wx74a8627810cfa300","secret":"e40a02f9cf9d79df497e6aaf93ab80"}res=requests.request(method="get",url=url,params=data)print(res.json())TestRequest access_token = res.json()['access_token']# post请求:编辑标签接口def test_edit_flag(self):url:"https://api.weixin.qq.com/cgi-bin/tags/updat?access token="+TestRequest.access_tokendata=["tag":{"id":134,"name":"广东人"}]str_data=json.dumps(data)res=requests.request(method="post",url=url,data=str_data)print(res.json())# 文件上传 def test_file_upload(self):url:"https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token="+TestRequest.access_token"+TestRequest.access_tokendata={"media":open(r"E:\shu.png","rb")}res=requests.request(method="post",url=url,files=data)print(res.json())# 访问首页的接口 def test_start(self):url:"https://47.107.116.139/phpwind"res=TestRequest.sess.request(method="get",url=url)# print(res.text) # 正则提取token obj=re.search('name="csrf_token" value="(.*?)"',res.text) # print(obj.group(1)) TestRequest.csrf_token=obj.group(1)# 登录接口 def test_login(self):url:"https://47.107.116.139/phpwind/index.php?m=u&c=login&a=dorun"data={"username":"msxy","password":"msxy","csrf_token":TestRequest.csrf_token,"backurl":"http://47.107.116.139/phpwind/","invite":""}# 请求头headers={"Accept":"application/json, text/javascript, /; q=0.01","X-Requested-with":"XMLHttpRequest"}res=TestRequest.sess.request(method="post",url=url,data=data,headers=headers)print(res.json())if __name__ == '__main__':TestRequest().test_get_token()TestRequest().test_edit_flag()TestRequest().test_file_upload()TestRequest().test_start()TestRequest().test_login()
pytest接口自动化-进阶篇
目标:python+requests+pytest+allure+jenkins
接口自动化测试框架关于接口关联的封装
策略:去掉全局变量,用yaml文件代替保存下载pyyaml的包(搜搜)
import os
import yaml# 读取
def read_yaml():with open(os.getcwd()+'/extract.yaml',mode='r',encoding='utf-8') as f:value = yaml.load(stream=f, Loader=yaml.FullLoader)return 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)# 清空
def clear_yaml(data):with open(os.getcwd()+'/extract.yaml', mode='w',encoding='utf-8') as f:f.truncate()
关于接口自动化框架之统一请求封装
如果两个测试用例要使用同一个session怎么办
-
通过同一个session发送请求
-
方便统计用例。日志封装
import requests
# 关于接口自动化框架之统一请求封装
class RequestUtil:# 全局变量 类变量 通过类名调用sess=requests.session()def send_request(self, url, method, datas=None, **kwargs):method=str(method).lower()res=Noneif method=='get':res=RequestUtil.sess.request(method=method,url=url,params=datas,**kwargs)elif method=='post':if datas and isinstance(datas,dict):datas=json.dumps(datas)res=RequestUtil.sess.request(method=method,url=url,datas=datas,**kwargs)else:passreturn res
P18接口自动化框架封装之Yaml数据驱动封装
步骤
1.将接口的数据的格式按照这种方式书写:
-name:request:method: geturl: https://api.weixin.qq.com/cgi-bin/tokendata:grant_type: client_credientialappid: wx6b11b3efd1cdc290secret: 106a9c61571dfdf55f6255cdgs5622validate: None
2.在yaml_util新增一个读取测试用例的方法
# 读取测试用例的方法
def read_testcase(yaml_name):with open(os.getcwd()+'/testcases/'+yaml_case,mode='r',encoding='utf-8') as f:value = yaml.load(stream=f, Loader=yaml.FullLoader)return value
3.在test_api.py 通过装饰器读取字典
如:
@pytest.mark.smoke
@pytest.mark.parametrize("args_name",read_testcase('get_token.yaml'))
def test_get_token(self,args_name):url=args_name['request']['url']data=args_name['request']['data']method=args_name['request']['method']res=RequestUtil().send_request(method=method,url=url,datas=data )