接口自动化测试_L2

目录:

  1. 接口请求体-文件
    1. 文件上传接口场景
    2. 使用 requests 如何上传
  2. 接口请求体-form表单
    1. ​​​​​​​什么是 FORM 请求
    2. 如何使用?
  3. 接口请求体-xml​​​​​​​
  4. xml响应断言
    1. ​​​​​​​​​​​​​​什么是 XML
    2. XML 断言
    3. XPath 断言
    4. XML 解析
  5. cookie处理
    1. Cookie简介​​​​​​​
  6. 超时处理
    1. ​​​​​​​请求超时
    2. 为什么接口测试需要请求超时处理?
    3. 如何设置
  7. 代理配置
    1. ​​​​​​​使用代理之前
    2. 使用代理之后
    3. 代理在接口自动化的使用场景
    4. Requests 代理
    5. 如何使用
  8. 多层嵌套响应断言
    1. ​​​​​​​什么是多层嵌套结构
    2. 复杂场景响应提取
    3. JSONPath 简介
    4. JSONPath 对比
    5. JSONPath 如何使用
    6. JSONPath 语法
    7. JSONPath 的练习环境
    8. JSONPath 练习题目
    9. JSONPath 练习
    10. JSONPath 与代码结合(Python)
  9. 宠物商店接口自动化测试实战
    1. ​​​​​​​​​​​​​​被测产品
    2. 需求说明
    3. 相关知识点
    4. 实战思路
    5. 需求分析
    6. 宠物管理接口业务流程测试用例
    7. 编写自动化测试脚本思路
    8. 编写自动化测试脚本
    9. 脚本优化 - 配置代理查看接口数据
    10. 脚本优化 - 添加日志
    11. 脚本优化 - 使用 jsonpath 断言
    12. 生成测试报告
    13. 总结
    14. 项目结构:
    15. 项目地址:

1.接口请求体-文件

 文件上传接口场景
  • 1867 文档中说明文件上传是一种常见的需要求,但是使用 html 中的 form 表单格式却不支持,
  • 提出了一种兼容此需求的 mime type。
  • 解决接口测试流程中文件上传的问题
    • 指定 name
    • 指定 filename
    • 指定 content-type
使用 requests 如何上传 
import requests


# key值需要替换为文件名
# value 对应为文件二进制流
def test_file():
    r = requests.post("https://httpbin.ceshiren.com/post",
                      # files={"request_file": open("./datas/1.txt", "rb")},
                      files={"request_file": ("hello.txt", open("./datas/1.txt", "rb"))},
                      verify=False
                      )

    print(r.text)

抓包看请求体:(学会使用charles或者fiddler抓包,看请求和响应)

2.接口请求体-form表单

什么是 FORM 请求
  • 数据量不大
  • 数据层级不深的情况
  • 通常以键值对传递
如何使用?
import requests


def test_form():
    data = {"hello": "form"}
    r = requests.post("https://httpbin.ceshiren.com/post", verify=False, data=data)
    print(r.text)


def test_json():
    data = {"hello": "form"}
    r = requests.post("https://httpbin.ceshiren.com/post", verify=False, json=data)
    print(r.text)

3.接口请求体-xml

import requests
import xml.etree.ElementTree as ET


def test_xml():
    # 读取XML文件
    tree = ET.parse('./datas/test.xml')
    root = tree.getroot()

    # 创建一个空列表来保存数据
    datas = []

    # 遍历XML文件的每个元素
    for elem in root.iter():
        # 将元素的标签和文本内容添加到列表中
        datas.append((elem.tag, elem.text))

    data = datas
    r = requests.post("https://httpbin.ceshiren.com/post", verify=False, data=data)
    print(r.text)

运行抓包:

4.xml响应断言

 什么是 XML
  • 可扩展标记语言(Extensible Markup Language)的缩写
  • 也是一种结构化的数据
XML 断言 
from requests_xml import XMLSession


def test_xml():
    session = XMLSession()
    r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
    print(r.text)
    print(r.xml.links)
    print(r.raw_xml)
    print(r.xml.text)
XPath 断言 
from requests_xml import XMLSession

session = XMLSession()

r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')

r.xml.links

item = r.xml.xpath('//item', first=True)

print(item.text)
from requests_xml import XMLSession
import xml.etree.ElementTree as ET


def test_xml():
    session = XMLSession()
    r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
    print(r.text)
    print(r.xml.links)
    print(r.xml.raw_xml)
    print(r.xml.text)


def test_xpath_assert():
    session = XMLSession()
    r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
    item = r.xml.xpath('//link')
    result = []
    for i in item:
        print(i.text)
        result.append(i.text)
    print(result)
    assert "https://www.nasa.gov/image-of-the-day/" in result

XML 解析 
import xml.etree.ElementTree as ET

root = ET.fromstring(countrydata)

root.findall(".")

root.findall("./country/neighbor")

root.findall(".//year/..[@name='Singapore']")

root.findall(".//*[@name='Singapore']/year")

root.findall(".//neighbor[2]")
from requests_xml import XMLSession
import xml.etree.ElementTree as ET


def test_xml():
    session = XMLSession()
    r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
    print(r.text)
    print(r.xml.links)
    print(r.xml.raw_xml)
    print(r.xml.text)


def test_xpath_assert():
    session = XMLSession()
    r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
    item = r.xml.xpath('//link')
    result = []
    for i in item:
        print(i.text)
        result.append(i.text)
    print(result)
    assert "https://www.nasa.gov/image-of-the-day/" in result


def test_xml_assert():
    session = XMLSession()
    r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
    root = ET.fromstring(r.text)
    item = root.findall(".")
    print(item)
    items = root.findall(".//link")
    print(items)

    for i in items:
        print(i.text)

5.cookie处理

Cookie简介

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

  • 通过请求头信息传递
  • 通过请求的关键字参数cookies传递
import requests


def test_cookie1():
    url = "https://httpbin.testing-studio.com/cookies"
    header = {"Cookie": "LiMing=teacher", "User-Agent": "hdc"}
    r = requests.get(url=url, headers=header, verify=False)
    print(r.request.headers)


def test_cookie2():
    url = "https://httpbin.testing-studio.com/cookies"
    header = {"User-Agent": "hdc"}
    cookie_data = {"today": "20231013"}
    r = requests.get(url=url, headers=header, verify=False, cookies=cookie_data)
    print(r.request.headers)

6.超时处理

请求超时

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

不设置请求超时:

设置请求超时: 

如何设置
import requests

class TestReq:

    def test_timeout(self):
        r = requests.get('http://github.com', timeout = 0.01)

代码示例:

import requests


class TestReq:

    def test_timeout(self):
        r = requests.get('http://github.com', timeout=0.01)
        # r = requests.get('http://github.com', timeout=5000)

7.代理配置

使用代理之前

使用代理之后 

代理在接口自动化的使用场景 
  • 测试脚本,更直观的排查请求错误,相当于编写代码时的 debug
  • 获取没有错误的,真实的接口请求响应信息
  • 通过代理获取自动化测试的请求响应
  • 对比两次请求响应的区别
Requests 代理 

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

如何使用
  1. 设定代理格式
  2. 通过 proxies 参数传递代理设置
  3. 开启代理工具监听请求

 

import requests


def test_proxy():
    proxy = {
        "http": "http://127.0.0.1:6666",
        "https": "http://127.0.0.1:6666"
    }
    url = "https://httpbin.testing-studio.com/post"
    r = requests.post(url=url, proxies=proxy, verify=False)
    print(r.text)

8.多层嵌套响应断言

什么是多层嵌套结构
// - 层级多。
// - 嵌套关系复杂。
{
  "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..name
JSONPath 如何使用
  • 语法知识。
  • 第三方库调用。
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
所有作者$..author
store 下面的所有内容$.store.
所有的价格$.store..price
第三本书$..book[2]
所有包含 isbn 的书籍$..book[?(@.isbn)]
所有价格小于 10 的书$.store.book[?(@.price < 10)]
所有书籍的数量$..book.length
JSONPath 与代码结合(Python) 
  • 环境安装:pip install jsonpath
# 具体的使用。
jsonpath.jsonpath(源数据对象, jsonpath表达式)

代码示例:

import jsonpath
import requests


def test_jsonpath():
    url = "https://httpbin.ceshiren.com/post"
    req_body = {"teacher": "ad", "school": "hdc"}
    r = requests.post(url, json=req_body, verify=False)
    print(r.json()["headers"]["Content-Type"])
    res = jsonpath.jsonpath(r.json(), "$..Content-Type")
    print(res)

    assert res[0] == r.json()["headers"]["Content-Type"]

9.宠物商店接口自动化测试实战

被测产品
  • PetStore 宠物商城:
    • 一个在线的小型的商城。
    • 主要提供了增删查改等操作接口。
    • 结合 Swagger 实现了接口的管理。

需求说明
  • 完成宠物商城宠物管理功能接口自动化测试。
    • 编写自动化测试脚本。
    • 完成复杂断言。
相关知识点
形式章节描述
知识点代理配置利用代理分析测试脚本,排查请求错误
知识点多层嵌套响应断言利用 jsonpath 进行多层嵌套的响应断言
实战思路

需求分析
  • 被测产品:宠物商店系统 - 宠物管理。
  • 宠物商店接口文档:https://petstore.swagger.io/
  • 宠物管理业务场景:
    • 添加宠物。
    • 查询宠物信息。
    • 修改宠物信息。
    • 删除宠物。

宠物管理接口业务流程测试用例

编写自动化测试脚本思路

编写自动化测试脚本
import requests


class TestPetstorePetmanager:
    def setup_class(self):
        self.base_url = "https://petstore.swagger.io/v2/pet"
        self.search_url = self.base_url + "/findByStatus"
        self.pet_id = 9223372000001084222
        self.delete_url = self.base_url + f'/{self.pet_id}'
        self.pet_status = "available"
        self.add_pet_info = {
            "id": self.pet_id,
            "category": {
                "id": 1,
                "name": "cat"
            },
            "name": "miao",
            "photoUrls": [
                "string"
            ],
            "tags": [
                {
                    "id": 5,
                    "name": "cute"
                }
            ],
            "status": self.pet_status
        }
        self.update_name = "miao-hdc"
        self.update_pet_info = {
            "id": self.pet_id,
            "category": {
                "id": 1,
                "name": "cat"
            },
            "name": self.update_name,
            "photoUrls": [
                "string"
            ],
            "tags": [
                {
                    "id": 5,
                    "name": "cute"
                }
            ],
            "status": self.pet_status
        }
        self.search_param = {
            "status": self.pet_status
        }

    def test_pet_manager(self):
        # 新增宠物接口
        add_r = requests.post(self.base_url, json=self.add_pet_info, verify=False)
        # 查询宠物
        search_r = requests.get(self.search_url, params=self.search_param, verify=False)
        # 修改宠物
        update_r = requests.put(self.base_url, json=self.update_pet_info, verify=False)
        # 删除宠物
        delete_r = requests.delete(self.delete_url, verify=False)

脚本优化 - 配置代理查看接口数据
  • 在脚本中配置代理。
  • 抓包查看接口测试中的接口请求和响应数据。
proxy = {
    "http": "http://127.0.0.1:8888",
    "https": "http://127.0.0.1:8888"
}

requests.post(url, json=pet_info, proxies=proxy, verify=False)
 脚本优化 - 添加日志
  • 新建日志配置。
  • 在用例中使用配置好的日志实例。
import logging
import os

from logging.handlers import RotatingFileHandler

# 绑定绑定句柄到logger对象
logger = logging.getLogger(__name__)

# 获取当前工具文件所在的路径
root_path = os.path.dirname(os.path.abspath(__file__))

# 拼接当前要输出日志的路径
root_len = len(root_path)
strs = root_path[0:root_len-6]
log_dir_path = os.sep.join([strs,f'datas\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,
                                       encoding="utf-8")

# 设置日志的格式
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)


def prit_path():
    print(root_path)
    print(log_dir_path)

prit_path()
 脚本优化 - 使用 jsonpath 断言
  • 使用 jsonpath 实现多层嵌套响应的断言。 
jsonpath.jsonpath(r.json(), "$..id")
import jsonpath
import requests

from interface_automation_testing.接口自动化测试_L2.宠物商店接口自动化测试实战.utils.log_util import logger


class TestPetstorePetmanager:
    def setup_class(self):
        self.base_url = "https://petstore.swagger.io/v2/pet"
        self.search_url = self.base_url + "/findByStatus"
        self.pet_id = 9223372000001084222
        self.delete_url = self.base_url + f'/{self.pet_id}'
        self.pet_status = "available"
        self.add_pet_info = {
            "id": self.pet_id,
            "category": {
                "id": 1,
                "name": "cat"
            },
            "name": "miao",
            "photoUrls": [
                "string"
            ],
            "tags": [
                {
                    "id": 5,
                    "name": "cute"
                }
            ],
            "status": self.pet_status
        }
        self.update_name = "miao-hdc"
        self.update_pet_info = {
            "id": self.pet_id,
            "category": {
                "id": 1,
                "name": "cat"
            },
            "name": self.update_name,
            "photoUrls": [
                "string"
            ],
            "tags": [
                {
                    "id": 5,
                    "name": "cute"
                }
            ],
            "status": self.pet_status
        }
        self.search_param = {
            "status": self.pet_status
        }

    def test_pet_manager(self):
        # 新增宠物接口
        add_r = requests.post(self.base_url, json=self.add_pet_info, verify=False)
        logger.info(f"新增宠物接口响应为:{add_r.text}")

        assert add_r.status_code == 200

        # 查询宠物
        search_r = requests.get(self.search_url, params=self.search_param, verify=False)
        logger.info(f"查询宠物接口的响应为:{search_r.text}")

        assert search_r.status_code == 200
        assert self.pet_id in jsonpath.jsonpath(search_r.json(), "$..id")

        # 修改宠物
        update_r = requests.put(self.base_url, json=self.update_pet_info, verify=False)
        logger.info(f"修改宠物接口的响应为:{update_r.text}")

        assert update_r.status_code == 200
        search_r = requests.get(self.search_url, params=self.search_param, verify=False)
        assert search_r.status_code == 200
        assert self.update_name in jsonpath.jsonpath(search_r.json(), "$..name")

        # 删除宠物
        delete_r = requests.delete(self.delete_url, verify=False)
        logger.info(f"删除宠物接口的响应为:{delete_r.text}")

        assert delete_r.status_code == 200
        search_r = requests.get(self.search_url, params=self.search_param, verify=False)
        assert search_r.status_code == 200
        assert self.pet_id not in jsonpath.jsonpath(search_r.json(), "$..id")

 

生成测试报告
  • 安装 allure 相关依赖。
# 生成报告信息
pytest --alluredir=./report
# 生成报告在线服务,查看报告
allure serve ./report/
pytest -vs .\test_petstore.py --alluredir=./datas/report --clean-alluredir

allure serve ./datas/report

 运行结果:

 

总结
  • 通过 Swagger 文档获取接口信息。
  • 使用 Requests 发出请求。
  • 添加代理,抓包查看接口请求和响应数据。
  • 使用 Jsonpath 提取复杂结构响应数据,然后进行断言。
  • 添加 Log 日志。
  • 生成 Allure 测试报告。
项目结构:

项目地址:

 https://gitee.com/coderPatrickStar/template/tree/master/python_software_testing

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿瞒有我良计15

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值