写点什么

软件测试 / 测试开发丨学习笔记之接口自动化测试

作者:测试人
  • 2023-05-29
    北京
  • 本文字数:9973 字

    阅读完需:约 33 分钟

获取更多相关知识


本文为霍格沃兹测试开发学社学员学习笔记分享,文末附原文链接

一、接口请求体-文件

1、通过接口上传文件

  • 辨别文件上传接口,查看 Content-Type

proxies




2、文件上传接口的场景

  • 解决接口测试流程中文件上传的问题指定 name 指定 filename 指定 content-type

3、 使用 requests 上传

import requests
url = "https://httpbin.ceshiren.com/post"# r = requests.post(url,# # files参数用来解决文件上传接口# files={"hogwarts_file": open("1.text", "rb")},# proxies={"http": "http://127.0.0.1:8080",# "https": "http://127.0.0.1:8080"},# verify=False# )
r = requests.post(url, # files参数用来解决文件上传接口 # value通过元组传递,实现指定filename files={"hogwarts_file": ("test.txt", open("1.text", "rb"))}, proxies={"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}, verify=False )print(r.json())
复制代码




二、接口请求体-form 表单

1、 FORM 请求

  • 数据量不大

  • 数据层级不深的情况

  • 通常以键值对传递

proxies



2、 使用 FORM 请求

import requests
class TestForm:
def setup_class(self): # 设置代理 self.proxy = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
def test_data(self): url = "https://httpbin.ceshiren.com/post" datas = {"a": 1} # 通过data参数传入请求体信息 r = requests.post(url, data=datas, proxies=self.proxy, verify=False) print(r.json())
复制代码



三、接口请求体-xml

1、复杂数据解析

  • 数据保存:将复杂的 xml 或者 json 请求体保存到文件模板中

  • 数据处理:使用 mustache、freemaker 等工具解析简单的字符串替换使用 json、xml、api 进行结构化解析

  • 数据生成:输出最终结果



四、xml 响应断言

1、 XML 介绍

  • 可扩展标记语言(Extensible Markup Language)的缩写

  • 也是一种结构化的数据

2、 XML 断言

from requests_xml import XMLSession
def test_xml(): # 设置session session = XMLSession() r = session.get("https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss") # 文本格式打印响应内容 print(r.text) # links可以把响应页面中的所有链接提取出来,并返回一个列表 print(r.xml.links) # raw_xml返回了字节形式的响应内容 print(r.xml.raw_xml) # text 返回标签中的内容 print(r.xml.text)
复制代码

3、 XPath 断言

from requests_xml import XMLSession
def test_xpath(): session = XMLSession() r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss') # 第一个参数为xpath的表达式,first参数表示是否只返回第一个查找的结果,默认False 把所有找到的对象以列表返回,True 表示把查找到的第一个结果返回 item = r.xml.xpath('//link', first=True) # 拿到Element对象 print(item) # 调用text属性,拿到link标签下的内容 print(item.text)
# 提取全部,需要进行循环遍历 item1 = r.xml.xpath('//link') # 定义一个列表,存放提取的内容 result = [] for i in item1: # 拿到所有link标签的内容,是单个对象,不是列表 print(i.text) # 返回的内容放在列表中 result.append(i.text) # 打印列表内容 print(result) assert "http://www.nasa.gov/" in result
复制代码

4、 XML 解析

# as ET 起别名import xml.etree.ElementTree as ETfrom requests_xml import XMLSession
def test_xml_tree(): # 设置session session = XMLSession() r = session.get("https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss")
# 自定义封装 xml 解析方法 # 调用fromstring方法,将提取的响应内容放到该方法里,得到整个根元素赋给root root = ET.fromstring(r.text) # 调用findall方法,使用xpath表达式,查找根元素 item = root.findall(".") # 是一个element对象,根元素没有属性,只是遍历子元素的接口 print(item) # 通过xpath查找子元素,查找link标签下的子元素 items = root.findall(".//link") result = [] for i in items: # # 拿到所有link标签的内容 # print(i.text) # 返回的内容放在列表中 result.append(i.text) # 打印列表内容 print(result) assert "http://www.nasa.gov/" in result
复制代码

五、cookie 处理

1、Cookie 简介

  • Cookie 使用场景在接口测试过程中,很多情况下,需要发送的请求附带 cookies,才会得到正常的响应的结果。所以使用 python+requests 进行接口自动化测试也是同理,需要在构造接口测试用例时加入 cookie

  • 传递 Cookie 的两种方式通过请求头信息传递通过请求的关键字参数 cookies 传递

2、自定义 header


import requests
class TestReqCookie: def setup_class(self): # 设置代理 self.proxy = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
def test_req_header(self): url = "https://httpbin.ceshiren.com/cookies" header = { "Cookie": "hogwarts=school", "User-Agent": "hogwarts" } # 注意:Cookie首字母要大写,且没有s r = requests.get(url=url, headers=header, proxies=self.proxy, verify=False) print(r.request.headers)
复制代码



3、使用 cookies 参数传递


import requests
class TestReqCookie: def setup_class(self): # 设置代理 self.proxy = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
def test_req_cookie(self): self.url = "https://httpbin.ceshiren.com/cookies" header = { "User-Agent": "hogwarts" } # 注意:Cookie首字母要大写,且没有s # 字典,键值对格式,可以传多条cookie cookie_data = { "hogwarts": "school", "teacher": "AD" } r = requests.get(url=self.url, headers=header, cookies=cookie_data, proxies=self.proxy, verify=False) print(r.request.headers)
复制代码




六、超时处理

1、 为什么接口测试需要请求超时处理



2、设置超时处理

import requests
class TestReqTime: def setup_class(self): # 设置代理 self.proxy = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
def test_001(self): # 通过timeout参数设置超时时间,参数对应的值通常是一个数字类型 r = requests.post('https://httpbin.ceshiren.com/post', timeout=3, proxies=self.proxy, verify=False) print(r.json())
def test_002(self): # 通过timeout参数设置超时时间,参数对应的值通常是一个数字类型 r = requests.post('https://httpbin.ceshiren.com/post', timeout=3, proxies=self.proxy, verify=False) print(r.json())
def test_003(self): # 通过timeout参数设置超时时间,参数对应的值通常是一个数字类型 r = requests.post('https://httpbin.ceshiren.com/post', timeout=0.01, proxies=self.proxy, verify=False) print(r.json())
复制代码



七、 代理配置

1、 使用代理之前


2、 使用代理之后


3、 代理在接口自动化的使用场景

  • 测试脚本,更直观的排查请求错误,相当于编写代码时的 debug

  • 获取没有错误的,真实的接口请求响应信息

  • 通过代理获取自动化测试的请求响应

  • 对比两次请求响应的区别

4、 Requests 代理

  • 通过 proxies 参数,监听请求与响应信息


5、 如何使用代理

  • 设定代理格式

  • 通过 proxies 参数传递代理设置

  • 开启代理工具监听请求

import requests

class TestProxt: def setup_class(self): # 定义一个代理的配置信息的变量,key值为协议,value 为代理工具的配置 self.proxy = { "http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080" }
def test_proxy(self): # 通过proxies 传递代理配置 self.data = {"a": 1} r = requests.post(url="https://httpbin.ceshiren.com/post", proxies=self.proxy, json=self.data, verify=False) print(r.json())
复制代码



八、多层嵌套响应断言

1、 多层嵌套结构

// - 层级多。// - 嵌套关系复杂。{  "errcode": 0,  "errmsg": "ok",  "userid": "zhangsan",  "name": "张三",  "department": [1, 2],  "order": [1, 2],  "position": "后台工程师",  "mobile": "13800000000",  "gender": "1",  "email": "zhangsan@gzdev.com",  "biz_mail": "zhangsan@qyycs2.wecom.work",  "is_leader_in_dept": [1, 0],  "direct_leader": ["lisi", "wangwu"],  "avatar": "http://wx.qlogo.cn/mmopen/ajNVdqHZLLA3WJ6DSZUfiakYe37PKnQhBIeOQBO4czqrnZDS79FH5Wm5m4X69TBicnHFlhiafvDwklOpZeXYQQ2icg/0",  "thumb_avatar": "http://wx.qlogo.cn/mmopen/ajNVdqHZLLA3WJ6DSZUfiakYe37PKnQhBIeOQBO4czqrnZDS79FH5Wm5m4X69TBicnHFlhiafvDwklOpZeXYQQ2icg/100",  "telephone": "020-123456",  "alias": "jackzhang",  "address": "广州市海珠区新港中路",  "open_userid": "xxxxxx",  "main_department": 1,  "extattr": {    "attrs": [      {        "type": 0,        "name": "文本名称",        "text": {          "value": "文本"        }      },      {        "type": 1,        "name": "网页名称",        "web": {          "url": "http://www.test.com",          "title": "标题"        }      }    ]  },  "status": 1,  "qr_code": "https://open.work.weixin.qq.com/wwopen/userQRCode?vcode=xxx",  "external_position": "产品经理",  "external_profile": {    "external_corp_name": "企业简称",    "wechat_channels": {      "nickname": "视频号名称",      "status": 1    },    "external_attr": [      {        "type": 0,        "name": "文本名称",        "text": {          "value": "文本"        }      },      {        "type": 1,        "name": "网页名称",        "web": {          "url": "http://www.test.com",          "title": "标题"        }      },      {        "type": 2,        "name": "测试app",        "miniprogram": {          "appid": "wx8bd80126147dFAKE",          "pagepath": "/index",          "title": "my miniprogram"        }      }    ]  }}复杂场景响应提取场景	方式提取 errcode 对应的值	res["errcode"]提取 title 对应的值	res["extattr"]["external_profile"]["external_attr"][1]["web"]["title"]提取 type 为 0 的 name	编码实现提取 attrs 下的所有的 name	编码实现JSONPath 简介在 JSON 数据中定位和提取特定信息的查询语言。JSONPath 使用类似于 XPath 的语法,使用路径表达式从 JSON 数据中选择和提取数据。相比于传统的提取方式,更加灵活,并且支持定制化。JSONPath 对比场景	对应实现	JSONPath 实现提取 errcode 对应的值	res["errcode"]	$.errcode提取 title 对应的值	res["extattr"]["external_profile"]["external_attr"][1]["web"]["title"] 等	$..title提取 type 为 0 的 name	编码实现	$..external_attr[?(@.type==0)].name提取 attrs 下的所有的 name	编码实现	$..attrs..nameJSONPath 如何使用语法知识。第三方库调用。JSONPath 语法符号	描述$	查询的根节点对象,用于表示一个 json 数据,可以是数组或对象@	过滤器(filter predicate)处理的当前节点对象*	通配符.	获取子节点..	递归搜索,筛选所有符合条件的节点?()	过滤器表达式,筛选操作[start:end]	数组片段,区间为[start,end),不包含 end[A]或[A,B]	迭代器下标,表示一个或多个数组下标JSONPath 的练习环境https://jsonpath.hogwarts.ceshiren.com/{  "store": {    "book": [      {        "category": "reference",        "author": "Nigel Rees",        "title": "Sayings of the Century",        "price": 8.95      },      {        "category": "fiction",        "author": "Evelyn Waugh",        "title": "Sword of Honour",        "price": 12.99      },      {        "category": "fiction",        "author": "Herman Melville",        "title": "Moby Dick",        "isbn": "0-553-21311-3",        "price": 8.99      },      {        "category": "fiction",        "author": "J. R. R. Tolkien",        "title": "The Lord of the Rings",        "isbn": "0-395-19395-8",        "price": 22.99      }    ],    "bicycle": {      "color": "red",      "price": 19.95    }  },  "expensive": 10}JSONPath 练习题目获取所有书籍的作者获取所有作者获取 store 下面的所有内容获取所有的价格获取第三本书获取所有包含 isbn 的书籍获取所有价格小于 10 的书获取所有书籍的数量JSONPath 练习需求	JsonPath所有书籍的作者	$.store.book[*].author所有作者	$..authorstore 下面的所有内容	$.store.*所有的价格	$.store..price第三本书	$..book[2]所有包含 isbn 的书籍	$..book[?(@.isbn)]所有价格小于 10 的书	$.store.book[?(@.price < 10)]所有书籍的数量	$..book.lengthJSONPath 与代码结合(Python)环境安装:pip install jsonpath# 具体的使用。jsonpath.jsonpath(源数据对象, jsonpath表达式)JSONPath 与代码结合(Java)
复制代码

2、 复杂场景响应提取

3、 JSONPath 简介

  • 在 JSON 数据中定位和提取特定信息的查询语言。

  • JSONPath 使用类似于 XPath 的语法,使用路径表达式从 JSON 数据中选择和提取数据。

  • 相比于传统的提取方式,更加灵活,并且支持定制化。

4、 JSONPath 对比

5、 JSONPath 如何使用

  • 语法知识。

  • 第三方库调用。

6、 JSONPath 语法








7、 JSONPath 练习

  • 获取所有书籍的作者


    $..book..author


  • 获取所有作者


    $..author


  • 获取 store 下面的所有内容


    $.store


  • 获取所有的价格


    $..price


  • 获取第三本书 (从 0 开始)


    $..book[2]


  • 获取所有包含 isbn 的书籍


    $..book[?(@.isbn)]


  • 获取所有价格小于 10 的书


    $..book[?(@.price<10)]


  • 获取所有书籍的数量(使用 length 函数)


    $..book.length


九、 宠物商店接口自动化测试实战

1、 实战思路


2、需求分析

  • 宠物管理业务场景:添加宠物。查询宠物信息。修改宠物信息。删除宠物。


3、自动化测试脚本思路


自动化代码

"""完成宠物商城宠物管理功能接口自动化测试。编写自动化测试脚本。完成复杂断言。"""import requests

class TestPetStore: def setup_class(self): # 定义pet_id self.pet_id = 9223372036854259066 # 定义请求url self.base_url = "https://petstore.swagger.io/v2/pet" # 拼接宠物查询接口url self.search_url = self.base_url + "/findByStatus" # 删除接口 self.delete_url = self.base_url + f"/{self.pet_id}" # 宠物状态 self.pet_status = "available" # 新增宠物信息 self.add_pet_info = { "id": self.pet_id, "category": { "id": 0, "name": "string" }, "name": "doggie", "photoUrls": [ "string" ], "tags": [ { "id": 0, "name": "string" } ], "status": self.pet_status } # 更新宠物数据 self.update_name = "maomao" self.update_pet_info = { "id": self.pet_id, "category": { "id": 0, "name": "string" }, "name": self.update_name, "photoUrls": [ "string" ], "tags": [ { "id": 0, "name": "string" } ], "status": self.pet_status } # 查询宠物数据 self.search_params = { "status": self.pet_status }
def test_pet_manager(self): # 新增宠物接口 add_r = requests.post(self.base_url, json=self.add_pet_info) assert add_r.status_code == 200 # 查询宠物接口 find_r = requests.get(self.search_url, params=self.search_params) assert find_r.status_code == 200 # 修改宠物 update_r = requests.put(self.base_url, json=self.update_pet_info) assert update_r.status_code == 200 # 删除宠物 delete_r = requests.delete(self.delete_url) assert delete_r.status_code == 200
复制代码

代码优化

  • 配置代理查看接口数据

  • 添加日志

  • 使用 jsonpath 断言使用 jsonpath 实现多层嵌套响应的断言。

日志配置:log_utils.py

# 配置日志import loggingimport os
from logging.handlers import RotatingFileHandler
# 绑定绑定句柄到logger对象logger = logging.getLogger(__name__)# 获取当前工具文件所在的路径root_path = os.path.dirname(os.path.abspath(__file__))# 拼接当前要输出日志的路径log_dir_path = os.sep.join([root_path, '..', f'/logs'])if not os.path.isdir(log_dir_path): os.mkdir(log_dir_path)# 创建日志记录器,指明日志保存路径,每个日志的大小,保存日志的上限file_log_handler = RotatingFileHandler(os.sep.join([log_dir_path, 'log.log']), maxBytes=1024 * 1024, backupCount=10)# 设置日志的格式date_string = '%Y-%m-%d %H:%M:%S'formatter = logging.Formatter( '[%(asctime)s] [%(levelname)s] [%(filename)s]/[line: %(lineno)d]/[%(funcName)s] %(message)s ', date_string)# 日志输出到控制台的句柄stream_handler = logging.StreamHandler()# 将日志记录器指定日志的格式file_log_handler.setFormatter(formatter)stream_handler.setFormatter(formatter)# 为全局的日志工具对象添加日志记录器# 绑定绑定句柄到logger对象logger.addHandler(stream_handler)logger.addHandler(file_log_handler)# 设置日志输出级别logger.setLevel(level=logging.INFO)
复制代码

优化后完整代码

"""完成宠物商城宠物管理功能接口自动化测试。编写自动化测试脚本。完成复杂断言。"""import allureimport jsonpathimport requestsfrom interface.utils.log_utils import logger
@allure.feature("宠物管理业务场景接口测试")class TestPetStore: def setup_class(self): # 代码优化,配置代理查看接口数据 self.proxy = { "http": "http://127.0.0.1:8888", "https": "http://127.0.0.1:8888" }
# 定义pet_id self.pet_id = 9223372036854259969 # 定义请求url self.base_url = "https://petstore.swagger.io/v2/pet" # 拼接宠物查询接口url self.search_url = self.base_url + "/findByStatus" # 删除接口 self.delete_url = self.base_url + f"/{self.pet_id}" # 宠物状态 self.pet_status = "available" # 新增宠物信息 self.add_pet_info = { "id": self.pet_id, "category": { "id": 0, "name": "string" }, "name": "doggie", "photoUrls": [ "string" ], "tags": [ { "id": 0, "name": "string" } ], "status": self.pet_status } # 更新宠物数据 self.update_name = "maomao" self.update_pet_info = { "id": self.pet_id, "category": { "id": 0, "name": "string" }, "name": self.update_name, "photoUrls": [ "string" ], "tags": [ { "id": 0, "name": "string" } ], "status": self.pet_status } # 查询宠物数据 self.search_params = { "status": self.pet_status }
@allure.story("宠物的增删改查场景测试") def test_pet_manager(self): # with allure.step添加步骤 with allure.step("新增宠物"): # 新增宠物接口 add_r = requests.post(self.base_url, json=self.add_pet_info, proxies=self.proxy, verify=False) # 添加日志 logger.info(f"新增宠物接口的响应为:{add_r.text}") # 断言 assert add_r.status_code == 200
with allure.step("查询宠物"): # 查询宠物接口 search_r = requests.get(self.search_url, params=self.search_params, proxies=self.proxy, verify=False) # 添加日志 logger.info(f"查询宠物接口的响应为:{search_r.text}") # 断言 assert search_r.status_code == 200 # 新增宠物业务断言,查询接口中是否包含新增宠物的id # 使用jsonpath提取响应内容,find_r.json()查询结果转换为json对象 search_js = jsonpath.jsonpath(search_r.json(), "$..id") assert self.pet_id in search_js
with allure.step("更新宠物"): # 更新宠物 update_r = requests.put(self.base_url, json=self.update_pet_info, proxies=self.proxy, verify=False) # 添加日志 logger.info(f"更新宠物接口的响应为:{update_r.text}") # 断言 assert update_r.status_code == 200 # 再次查询宠物 search_r1 = requests.get(self.search_url, params=self.search_params, proxies=self.proxy, verify=False) # 更新宠物的业务断言,查询接口中是否包含更新宠物的name update_js = jsonpath.jsonpath(search_r1.json(), "$..name") assert self.update_name in update_js
with allure.step("删除宠物"): # 删除宠物 delete_r = requests.delete(self.delete_url, proxies=self.proxy, verify=False) # 添加日志 logger.info(f"删除宠物接口的响应为:{delete_r.text}") # 断言 assert delete_r.status_code == 200 # 再次查询宠物 search_r2 = requests.get(self.search_url, params=self.search_params, proxies=self.proxy, verify=False) # 业务断言,查询接口中不包含宠物的id delete_js = jsonpath.jsonpath(search_r2.json(), "$..id") assert self.pet_id not in delete_js
复制代码

生成测试报告

# 生成报告信息 pytest -vs test_petsearch_l2.py --alluredir=./reports/# 生成报告在线服务,查看报告allure serve ./reports/ 
复制代码

原文链接:https://ceshiren.com/t/topic/25445

发布于: 刚刚阅读数: 3
用户头像

测试人

关注

专注于软件测试开发 2022-08-29 加入

霍格沃兹测试开发学社,测试人社区:https://ceshiren.com/t/topic/22284

评论

发布
暂无评论
软件测试/测试开发丨学习笔记之接口自动化测试_程序员_测试人_InfoQ写作社区