Pytest接口自动化1-从入门到进阶实战
一、为什么要用Pytest来做接口自动化
1.Pytest的用途及优点
Pytest目前是自动化业界非常主流的一个自动化测试框架,它本质是Python的一个第三方单元测试库。和unittest一样,主要是用来管理自动化测试用例的执行的,比如用例执行,用例分组,执行日志输出等等。
Pytest的优点:
1.他可以自动的识别测试模块(测试文件)、测试类以及测试函数,规则很简单:
文件名:格式为test _ *.py或* _test.py的文件
类:Test开头
测试函数/方法:拥有test前缀的测试函数或方法
2.模块化夹具fixture可用来管理各类测试资源
3.对unittest完全兼容
我们知道在pytest出来之前,unittest应用也极为广泛,因此完全兼容unittest,在想要对自动化框架升级的时候可以节省不少开发成本。
4.Pytest是最能 装“插”的开源单元测试框架
https://docs.pytest.org/en/latest/reference/plugin_list.html
目前pytest已经有900多个插件,每天都会新增1到2个插件。著名的插件有:
pytest-allure、pytest-rerunfailures、pytest-xdist等等
插件能力:集成功能强大的测试报告组件,实现失败重跑,并发用例执行等
插件的安装也非常简单,基本上就是:pip install 插件名
5.pytest安装
1)安装命令
pip install pytest
2)验证安装
pytest --version
3)官方文档
https://docs.pytest.org/en/latest/contents.html
以上这些,充分说明pytest是一个简单、易用并且功能强大的用例管理框架。
二、接口测试简介
接口组成元素:
1.接口地址(url+端口+路径)
2.接口请求方式: post get delete put...
3.接口请求参数
4.响应数据
这里演示使用postman完成登陆接口调用:
然后我们看到登陆成功后,响应报文里头有个token,这个东西是干嘛的呢,简单介绍一下:
token用途简介
登陆系统 = 进医院大门
token = 绿码
拥有了绿码才有进入医院的权限 = 拥有token才能访问系统内部的各个页面
没有绿码,不允许进入任何门诊 = 没有token,即使你拥有接口的链接,响应也会提示你鉴权失败
以上只是接口最简单的一个入门介绍,要真正的学好接口测试,要学的东西很多,比如必须熟悉的入门级的基础理论:接口通信原理、HTTP网络协议、接口鉴权机制等等,一节课肯定是没法熟悉接口自动化的,必须系统的去学习才能完整的掌握
三、用Python代码实现接口测试
相对于工具,用python做接口自动化的优势:
扩展性更强,更灵活
python 各种封装、调用
可集成各种库和工具,满足各种需求
allure 报告
jsonpath 报文解析
jenkins 持续集成
。。。
代码实操:Pytest_intf_advance/base_intf/demo.py
PS:这里的接口地址是自己本地搭建的一个接口测试服务,仅供大家参考,后期有时间再弄个免费的公网接口服务给大家使用
#!/usr/bin/env python # -*- coding: utf-8 -*- # @File : demo.py import json import jsonpath import requests from Pytest_intf_advance.base_intf.api_key import ApiKey # 接口请求的模拟 # 数据的生成 data = { 'username': 'admin', 'password': '123456' } # 接口的地址 url = 'http://127.0.0.1:5000/api/login' # 将数据传递到对应的接口地址,来实现一次该接口的请求下发并返回响应结果:定义对应的请求方法 res = requests.post(url=url, json=data) # 输出响应结果:编译后的内容 print(res.text)
# 返回报文中的某个key,比如msg来获取相应的值 print(type(res.text)) # 输出响应结果:字典类型 print(res.json()) print(type(res.json())) # 接口断言 assert 'success' == res.json()['msg'] # 接口断言,嵌套字典,{key:value,key:{key:value}} # assert 'changsha' == res.json()['city'] assert 'changsha' == res.json()['adress']['city'] # 那么这里我们就可以用到jsonpath库,来简化取值操作 # jsonpath获取数据的表达式:成功则返回list,失败则返回false # loads是将json格式的内容转换为字典的格式 # jsonpath接收的是dict类型的数据 value_list = jsonpath.jsonpath(res.json(), '$..{0}'.format('city')) print(value_list) value = value_list[0] print(value) assert 'changsha' == value # 就有点繁琐,因此我们引入接口关键字封装,来简化代码,封装api_key # 实例化工具类 ak = ApiKey() assert 'changsha' == ak.get_text(res.text,'city') print(ak.get_text(res.text,'city'))
代码执行输出信息:
{ "adress": { "city": "changsha" }, "httpstatus": 200, "info": { "age": 18, "name": "admin" }, "msg": "success", "token": "23657DGYUSGD126731638712GE18271H" } <class 'str'> {'adress': {'city': 'changsha'}, 'httpstatus': 200, 'info': {'age': 18, 'name': 'admin'}, 'msg': 'success', 'token': '23657DGYUSGD126731638712GE18271H'} <class 'dict'> ['changsha'] changsha changsha Process finished with exit code 0
四、接口关键字封装
代码实操:Pytest_intf_advance/base_intf/api_key.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # @File : api_key.py """ 这是接口关键字驱动类,用于提供自动化接口测试的关键字方法。 主要实现常用的关键字内容,并定义好所有的参数内容即可 """ import json import allure import jsonpath import requests class ApiKey: # 基于jsonpath获取数据的关键字:用于提取所需要的内容 def get_text(self,data,key): # jsonpath获取数据的表达式:成功则返回list,失败则返回false # loads是将json格式的内容转换为字典的格式 # jsonpath接收的是dict类型的数据 dict_data = json.loads(data) value = jsonpath.jsonpath(dict_data,'$..{0}'.format(key)) return value[0] # get请求的封装:因为params可能存在无值的情况,存放默认None def get(self,url,params=None,**kwargs): return requests.get(url=url,params=params,**kwargs) #post请求的封装:data也可能存在无值得情况,存放默认None def post(self,url,data=None,**kwargs): return requests.post(url=url,data=data,**kwargs) if __name__ == '__main__': ak = ApiKey() data = { 'username': 'admin', 'password': '123456' } res2 = ak.post(url='http://127.0.0.1:5000/api/login',json=data) print(res2.text)
代码执行输出信息:
{ "adress": { "city": "changsha" }, "httpstatus": 200, "info": { "age": 18, "name": "admin" }, "msg": "success", "token": "23657DGYUSGD126731638712GE18271H" }
那么我们第一个接口自动化代码就写完了,我们可以看到,上述代码是不是很松散。没有以一个用例的形式来进行,那么这时候我们就可以上Pytest了
五、用Pytest框架编写接口自动化测试代码
1.基本用例组织
pytest用例运行测规则很简单,从上往下,完全按你放置的顺序来执行
代码实操:Pytest_intf_advance/pytest_intf/case/test_shopXo.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # @File : test_shopXo.py import pytest import requests from Pytest_intf_advance_V2.base_intf.api_key import ApiKey class Test_ApiCase(): # 登陆接口用例 def test_login(self): url = 'http://127.0.0.1:5000/api/login' userInfo = { 'username': 'admin', 'password': '123456' } res = self.ak.post(url=url, json=userInfo) print(res.text) # 获取响应中的结果,用于校验是否成功 msg1 = self.ak.get_text(res.text, 'msg') print(msg1) assert msg1 == 'success' # 查询用户信息接口 def test_getuserinfo(self,token_fix): # 1.获取工具类、token ak, token = token_fix # 2.查询个人用户信息 url = 'http://127.0.0.1:5000/api/getuserinfo' headers = { 'token': token } res1 = ak.get(url=url, headers=headers) print(res1.text) name = ak.get_text(res1.text, 'nikename') assert "风清扬" == name if __name__ == '__main__': # -s参数,在控制台输出打印信息 # -v参数,在控制台输出详细信息 pytest.main(['-s','-v','test_shopXo_02.py'])
代码执行输出信息:
============================= test session starts ============================= platform win32 -- Python 3.8.10, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 -- D:\Python38\python.exe cachedir: .pytest_cache metadata: {'Python': '3.8.10', 'Platform': 'Windows-10-10.0.19044-SP0', 'Packages': {'pytest': '6.1.2', 'py': '1.9.0', 'pluggy': '0.13.1'}, 'Plugins': {'allure-pytest': '2.8.11', 'forked': '1.1.3', 'html': '3.0.0', 'metadata': '1.8.0', 'ordering': '0.6', 'parallel': '0.1.0', 'rerunfailures': '9.1.1', 'xdist': '1.31.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_152'} rootdir: D:\Pytest_intf_advance_V2\pytest_intf\case plugins: allure-pytest-2.8.11, forked-1.1.3, html-3.0.0, metadata-1.8.0, ordering-0.6, parallel-0.1.0, rerunfailures-9.1.1, xdist-1.31.0 collecting ... collected 2 items test_shopXo.py::Test_ApiCase::test_login { "adress": { "city": "changsha" }, "httpstatus": 200, "info": { "age": 18, "name": "admin" }, "msg": "success", "token": "23657DGYUSGD126731638712GE18271H" } success PASSED test_shopXo.py::Test_ApiCase::test_getuserinfo { "data": [ { "nikename": "王五", "openid": "UEHUXUXU78272SDSassDD", "userbalance": 5678.9, "userid": 17890, "username": "admin", "userpoints": 4321 } ], "httpstatus": 200 } PASSED ============================== 2 passed in 0.30s ============================== Process finished with exit code 0
2.接口关联
实现接口关联有多种方式:
1)和工具类相似使用setup_class,用的时候加个self就可以了
2)公共变量可以定义一个公共变量放到类里,这样类中的所有用例都可以读取到了
那么上面这两种方式,其实是有局限性的,就是只能在单个类或者单个文件中使用,如果需要在整个项目的多个测试文件中使用,就不行了,现在就给大家着重介绍第三种方式,来实现项目级的token预置
六、fixture+conftest实现项目级token预置
接下来,我们先讲讲fixture,官方的介绍比较复杂,这边我个人总结了下。
1.pytest之fixture介绍
fixture是pytest提供的一个简化的装饰器,可以轻松的复用已定义的函数逻辑(比如登陆,获取token,环境数据预置)等操作。
官方介绍:
https://docs.pytest.org/en/latest/explanation/fixtures.html#about-fixtures
概念很简单,关键是如何用,下面给大家编写一个fixture快速入门的例子
代码实操:Pytest_intf_advance/base_intf/test_fix/test_quick_exam.py
#coding=utf-8 import pytest # 通过@pytest.fixture声明这个函数为fixture # 用例前置 @pytest.fixture def first_fix(): # 此处假设有代码逻辑几十行,比如执行登陆操作,获取token之类的 # 返回一个list return ["a"] # 将已声明为fixture的函数,填写在参数中,这样fixture函数会在该函数调用之前调用 # 测试用例 def test_string(first_fix): # 用例步骤 # 使用first_fix返回的list添加元素 first_fix.append("b") print(first_fix) if __name__ == '__main__': pytest.main(['-s'])
代码执行输出信息:
============================= test session starts ============================= platform win32 -- Python 3.8.10, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 rootdir: D:\Pytest_intf_advance_V2\base_intf\test_fix plugins: allure-pytest-2.8.11, forked-1.1.3, html-3.0.0, metadata-1.8.0, ordering-0.6, parallel-0.1.0, rerunfailures-9.1.1, xdist-1.31.0 collected 3 items test_quick_exam.py ['a', 'b'] . test_conftest_01\doc1\test_case01.py 开始执行登陆操作 用例一 . test_conftest_01\doc2\test_case02.py 开始执行登陆操作 用例二 . ============================== 3 passed in 0.07s ============================== Process finished with exit code 0
以上就是fixture最基本的概念和用法。这时候,可能有人就想问了,那我想在其他文件中使用这个定义好的fixture怎么办呢?那这就要介绍下fixture的好基友,conftest.py配置文件了
2.conftest.py
conftest.py是pytest特有的本地测试配置文件,在这个文件中定义的Fixture可以在项目中多个文件使用,conftest.py文件名称是固定的,pytest会自动识别该文件,只作用于它所在的目录及子目录。
同样的,编写一个快速入门的代码例子:
代码实操:多文件代码,先看代码结构
Pytest_intf_advance/base_intf/test_fix/test_conftest_01/conftest.py
#coding=gbk import pytest @pytest.fixture() def fix1(): print("\n开始执行登陆操作")
Pytest_intf_advance/base_intf/test_fix/test_conftest_01/doc1/test_case01.py
#coding=gbk import pytest def test_case02(fix1): print("用例一")
Pytest_intf_advance/base_intf/test_fix/test_conftest_01/doc2/test_case02.py
#coding=gbk import pytest def test_case02(fix1): print("用例二")
Pytest_intf_advance/base_intf/test_fix/test_conftest_01/main_run.py
#coding=gbk import pytest if __name__ == '__main__': pytest.main(['-s'])
运行main_run.py文件
代码输出:
collected 2 items doc1\test_case01.py 开始执行登陆操作 用例一 . doc2\test_case02.py 开始执行登陆操作 用例二 . ============================== 2 passed in 0.05s ============================== Process finished with exit code 0
fixture+conftest的搭配是非常的有用的,接下来我们来在接口自动化中实战一把,来展现一下这对黄金组合的实力
3.接口中应用fix+conftest实现项目级token预置
1)先在conftest中定义fixture
初始化工具类,登陆,并返回工具类对象和token值
代码实操:多文件代码,先看代码结构
Pytest_intf_advance_V2/pytest_intf/conftest.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # @File : conftest.py from random import random from Pytest_intf_advance.base_intf.api_key import ApiKey import pytest # @pytest.fixture(scope='session') # def token_fix(): # a = random() # print(a) # return a #项目级fix,整个项目只初始化一次 @pytest.fixture(scope='session') def token_fix(): # 初始化工具类 ak = ApiKey() # 定义访问链接‘ url = 'http://127.0.0.1:5000/api/login' # 定义请求用户数据 userInfo = { 'username': 'admin', 'password': '123456' } # 发送post请求 res = ak.post(url=url,json = userInfo) # 获取token token = ak.get_text(res.text,'token') # 返回多个值 return ak,token
Pytest_intf_advance_V2\pytest_intf\api_keyword\api_key.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # @File : api_key.py """ 这是接口关键字驱动类,用于提供自动化接口测试的关键字方法。 主要实现常用的关键字内容,并定义好所有的参数内容即可 """ import json import allure import jsonpath import requests class ApiKey: # 基于jsonpath获取数据的关键字:用于提取所需要的内容 def get_text(self,data,key): # jsonpath获取数据的表达式:成功则返回list,失败则返回false # loads是将json格式的内容转换为字典的格式 # jsonpath接收的是dict类型的数据 dict_data = json.loads(data) value = jsonpath.jsonpath(dict_data,'$..{0}'.format(key)) return value[0] # get请求的封装:因为params可能存在无值的情况,存放默认None def get(self,url,params=None,**kwargs): return requests.get(url=url,params=params,**kwargs) #post请求的封装:data也可能存在无值得情况,存放默认None def post(self,url,data=None,**kwargs): return requests.post(url=url,data=data,**kwargs) if __name__ == '__main__': ak = ApiKey() # res = ak.get(url='http://127.0.0.1:5000/api/getuserinfo',timeout=0.1) # print(res.text) data = { 'username': 'admin', 'password': '123456' } res2 = ak.post(url='http://127.0.0.1:5000/api/login',json=data) print(res2.text)
Pytest_intf_advance_V2\pytest_intf\case\test_shopXo_02.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # @File : test_shopXo.py import pytest import requests from Pytest_intf_advance_V2.base_intf.api_key import ApiKey class Test_ApiCase(): # 查询用户信息接口 def test_getuserinfo(self,token_fix): # 1.获取工具类、token ak, token = token_fix # 2.查询个人用户信息 url = 'http://127.0.0.1:5000/api/getuserinfo' headers = { 'token': token } res1 = ak.get(url=url, headers=headers) print(res1.text) name = ak.get_text(res1.text, 'nikename') assert "张三" == name if __name__ == '__main__': # -s参数,在控制台输出打印信息 # -v参数,在控制台输出详细信息 pytest.main(['-s','-v','test_shopXo_02.py'])
代码输出:
collecting ... collected 1 item test_shopXo_02.py::Test_ApiCase::test_getuserinfo { "data": [ { "nikename": "张三", "openid": "UEHUXUXU78272SDSassDD", "userbalance": 5678.9, "userid": 17890, "username": "admin", "userpoints": 4321 } ], "httpstatus": 200 } PASSED ============================== 1 passed in 0.20s ============================== Process finished with exit code 0
通过fixture+conftest 这套组合,可以方便的在多个文件中使用同一个token,可以在项目内实现更大范围内的接口关联。
本文来自博客园,作者:测试老宅男扶摇,转载请注明原文链接:https://www.cnblogs.com/cekailsf/p/15919246.html