接口自动化测试-L1

一、 接口自动化测试框架介绍

1、 接口测试场景

2、 自动化测试场景

3、 接口自动化测试与 Web/App 自动化测试区别

  • 接口关注数据无法触达用户体验。

4、 接口测试工具类型

测试类型 工具 价值
接口抓包 Charles、Postman 接口抓包工具,可以抓取 App 的数据包
接口测试 Postman 接口调试工具,接口手工测试工具,学习成本低,直接安装即可使用
接口自动化测试 Requests、RestAssured 用于接口自动化测试的 Java、Python 第三方库,需要与对应编程语言结合使用
性能测试 JMeter 性能测试工具

5、 Requests

6、 Requests 优势

  • 功能全面:HTTP/HTTPS 支持全面。
  • 使用简单:简单易用,不用关心底层细节。
  • 定制性高:结合测试框架完成二次封装,比如 HttpRunner。
    image

7、 Requests 环境准备

  • 安装命令:pip install requests

二、接口请求方法

1、 常见 HTTP 请求方法构造

方法 说明
requests.request() 构造一个请求,支撑以下各方法的基础方法。
requests.get() 构造 HTTP 协议中的 GET 请求。
requests.post() 构造 HTTP 协议中的 POST 请求。
requests.put() 构造 HTTP 协议中的 PUT 请求。
requests.delete() 构造 HTTP 协议中的 DELETE 请求。

2、 底层设计

3、 HTTP 协议知识

  • URL 结构
  • HTTP 请求
  • HTTP 响应

3、 构造 GET 请求

  • requests.get(url, params=None, **kwargs)
    • url: 接口 url。
    • params:拼接在 url 中的请求参数,非必填,不填默认为None。
    • **kwargs:更多底层支持的参数。
# 导入依赖
import requests

def test_get():
    # 定义接口的 url 和拼接在 url 中的请求参数
    url = "https://httpbin.ceshiren.com/get"
    # 发出 GET 请求,r 接收接口响应
    r = requests.get(url)
    # 打印接口响应
    logger.info(f"接口响应为 {r}")

4、 构造 POST 请求

  • requests.post(url, data=None, json=None, **kwargs)
    • url: 接口 url。
    • data:表单格式请求体。
    • json:JSON 格式请求体。
    • **kwargs:更多底层支持的参数。
# 导入依赖
import requests

def test_post():
    # 定义接口的 url
    url = "https://httpbin.ceshiren.com/post"
    # 发出 POST 请求,r 接收接口响应
    r = requests.post(url)
    # 打印接口响应
    logger.info(f"接口响应为 {r}")

5、 构造 PUT 请求

  • requests.put(url, data=None, **kwargs)
    • url: 接口 url。
    • data:表单格式请求体。
    • **kwargs:更多底层支持的参数。
# 导入依赖
import requests

def test_put():
    # 定义接口的 url
    url = "https://httpbin.ceshiren.com/put"
    # 发出 POST 请求,r 接收接口响应
    r = requests.put(url)
    # 打印接口响应
    logger.info(f"接口响应为 {r}")

6、 构造 DELETE 请求

  • requests.delete(url, **kwargs)
    • url: 接口 url。
    • **kwargs:更多底层支持的参数。
# 导入依赖
import requests

def test_delete():
    # 定义接口的 url 和表单格式请求体
    url = "https://httpbin.ceshiren.com/delete"
    # 发出 POST 请求,r 接收接口响应
    r = requests.delete(url)
    # 打印接口响应
    logger.info(f"接口响应为 {r}")

实战代码

import requests

# 发起get请求
def test_res_get():
    url = "https://httpbin.ceshiren.com/get"
    # 发起get请求, 并返回一个响应对象
    r = requests.get(url)
    # 把响应对象以json的形式输出
    print(r.json())

# 发起post请求
def test_res_post():
    url = "https://httpbin.ceshiren.com/post"
    # 发起get请求, 并返回一个响应对象
    r = requests.post(url)
    # 打印文本格式的响应信息,如果出现method not allowed,代表请求方法使用错误
    print(r.text)

# 发起put请求
def test_res_put():
    url = "https://httpbin.ceshiren.com/put"
    # 发起get请求, 并返回一个响应对象
    r = requests.put(url)
    # 打印文本格式的响应信息,如果出现method not allowed,代表请求方法使用错误
    print(r.text)

# 发起delete请求
def test_res_delete():
    url = "https://httpbin.ceshiren.com/delete"
    # 发起get请求, 并返回一个响应对象
    r = requests.delete(url)
    # 打印文本格式的响应信息,如果出现method not allowed,代表请求方法使用错误
    print(r.text)

7、 构造请求方法

requests.request(method, url, **kwargs)

  • method: 请求方法。
    • GETOPTIONSHEADPOSTPUTPATCHDELETE
  • url: 接口 url。
  • **kwargs:更多底层支持的参

8、 底层参数说明

参数 应用场景
method 请求方法
url 请求 URL
params 请求中携带 URL 参数
data 请求中携带请求体(默认为表单请求)
json 请求中携带 json 格式的请求体
headers 请求中携带头信息
cookies 请求中携带 cookies
files 请求中携带文件格式的请求体
auth 请求中携带认证信息
timeout 设置请求超时时间
allow_redirects 请求是否允许重定向
proxies 设置请求代理
verify 请求是否要认证
cert 请求中携带 ssl 证书

image

三、接口请求参数

1、 请求参数简介

  • 接口请求中携带的,会在路径之后使用?代表客户端向服务端传递的参数。
  • 使用 key=value 形式拼接在 URL 中。
  • 如果有多个,则使用&分隔
  • 例如:
    image

2、 携带请求参数的方式

  • 常用两种方式:
    • 直接在 URL 中拼接:?username=Hogwarts&id=666
    • 通过 params 参数传递:requests.get(url, params)

3、 携带请求参数的 GET 请求

import requests

# 发起get请求
def test_get_url():
    # url中携带参数信息
    url = "https://httpbin.ceshiren.com/get?name=ad&age=18"
    r = requests.get(url)
    print(r.text)

def test_get_params():
    url = "https://httpbin.ceshiren.com/get"
    # 定义一个字典格式的变量信息
    req_params = {"name": "ad", "age": 18}
    # 通过params进行传参
    r = requests.get(url, params=req_params)
    print(r.text)

4、 携带请求参数的 POST 请求

import requests

# ===其他类型的请求也可以携带url参数信息
# 发起get请求
def test_post_url():
    # url中携带参数信息
    url = "https://httpbin.ceshiren.com/post?name=ad1&age=19"
    r = requests.post(url)
    print(r.text)


def test_post_params():
    # url不携带参数信息
    url = "https://httpbin.ceshiren.com/post"
    # 定义一个字典格式的变量信息
    req_params = {"name": "ad1", "age": 19}
    # 通过params进行传参
    r = requests.post(url, params=req_params)
    print(r.text)

四、接口请求头

1、 请求头信息的使用场景

  • 身份认证
  • 指定数据类型

例如:飞书接口文档

2、 请求头信息

  • HTTP 请求头是在 HTTP 请求消息中包含的元数据信息,用于描述请求或响应的一些属性和特征。
  • 实际工作过程中具体要关注的头信息字段需要和研发沟通
  • 常见的头信息(下面表格):
内容 含义
Authorization 表示客户端请求的身份验证信息
Cookie 表示客户端的状态信息,通常用于身份验证和会话管理
Content-Type 表示请求消息体的 MIME 类型
User-Agent 发送请求的客户端软件信息

3、 构造头信息

  • 使用 headers 参数传入。
  • 通常使用字典格式。
headers = {'user-agent': 'my-app/0.0.1'}
r = requests.get(url, headers=headers)
import requests

def test_res_get():
    url = "https://httpbin.ceshiren.com/get"
    # 通过字典格式定义一个头信息
    header = {"name": "ad", "User-Agent": "apple", "Content-type": "application/json"}
    r = requests.get(url, headers=header)
    print(r.text)

五、接口请求体-json

1、 接口请求体简介

  • 进行HTTP请求时,发送给服务器的数据。

  • 数据格式类型可以是JSON、XML、文本、图像等格式。

  • 请求体的格式和内容取决于服务器端API的设计和开发人员的要求。

  • 例如:飞书接口文档

2、常用接口请求体

类型 介绍 Content-type
JSON(JavaScript Object Notation) 轻量级的数据交换格式,最常见的一种类型。 application/json
表单数据(Form Data) 以键值对的形式提交数据,例如通过 HTML 表单提交数据。 application/x-www-form-urlencoded
XML(eXtensible Markup Language) 常用的标记语言,通常用于传递配置文件等数据。 application/xml
text/xml
文件(File) 可以通过请求体上传文件数据,例如上传图片、视频等文件。 上传文件的 MIME 类型,例如 image/jpeg
multipart/form-data
纯文本(Text) 纯文本数据,例如发送邮件、发送短信等场景 text/plain
其他格式 二进制数据、protobuf 等格式

3、 JSON 简介

  • JavaScript Object Notation 的缩写。
  • 是一种轻量级的数据交换格式。
  • 是理想的接口数据交换语言。
  • Content-Type 为 application/json。

4、构造 JSON 格式请求体

  • 定义为字典格式。
  • 使用 json 参数传入。
import requests

def test_body_json():
    # httpbin.ceshiren.com是请求什么返回什么
    url = "https://httpbin.ceshiren.com/post"
    # 定义变量,存放请求体
    req_body = {
        "name": "holy",
        "age": 19
    }
    # 通过json关键字传递请求体信息
    r = requests.post(url, json=req_body)
    print(r.text)

六、接口响应断言

1、 接口断言使用场景

  • 问题:
    1. 如何确保请求可以发送成功。
    2. 如何保证符合业务需求。
  • 解决方案:
    • 通过获取响应信息,验证接口请求是否成功,是否符合业务需求。

2、 Requests 中的响应结果对象

import requests
from requests import Response

# Response就是一个响应对象
r: Response = requests.get('http://www.example.com')

3、 响应结果类型

属性 含义
r 响应 Response 对象(可以使用任意的变量名)
r.status_code HTTP 响应状态码
r.headers 返回一个字典,包含响应头的所有信息。
r.text 返回响应的内容,是一个字符串。
r.url 编码之后的请求的 url
r.content 返回响应的内容,是一个字节流。
r.raw 响应的原始内容
r.json() 如果响应的内容是 JSON 格式,可以使用该方法将其解析成 Python 对象。

4、 响应状态码断言

  • 基础断言:
    • r.status_code
import requests

def test_res():
    url = "https://httpbin.ceshiren.com/get"
    # 返回值为一个Response对象
    r = requests.get(url)
    # 断言,确定响应状态码为200
    assert r.status_code == 200
    # 断言url
    assert r.url == "https://httpbin.ceshiren.com/get"

    # 打印响应状态码信息
    print(r.status_code)
    print("-----------")
    # 打印响应头信息
    print(r.headers)
    print("-----------")
    # 打印响应体信息
    print(r.text)
    print("-----------")
    # 打印编码之后的请求的 url
    print(r.url)

七、json响应体断言

1、 JSON 响应体

  • JSON格式的响应体指的是HTTP响应中的消息体(message body),它是以JSON格式编码的数据。
{
  "name": "John",
  "age": 30,
  "city": "New York"
}

2、断言 JSON 格式响应体使用场景

  • 验证API接口的返回结果是否符合预期。
    • 业务场景上是否符合预期。
    • 格式是否符合文档规范。

3、 断言 JSON 格式响应体

  • r.json():返回 python 字典。

4、 若碰到复杂断言应该如何处理?

  • 多层嵌套的数据提取与断言: JSONPath
  • 整体结构响应断言: JSONSchema
  • 自行编写解析算法
import requests


def test_res_json():
    url = "https://httpbin.ceshiren.com/get"
    # 如果响应体是非json的场景,就不能使用r.json()的方式,否则会报raise RequestsJSONDecodeError异常,可以使用r.text
    r = requests.get(url)
    print(r.json())
    # 断言响应状态码。只能判断请求是否成功发送
    assert r.status_code == 200
    # r.json()将json格式转换为字典,断言外层的某个字段数据,和业务场景相关
    assert r.json()["url"] == url
    # 断言多层的场景
    assert r.json()["headers"]["Host"] == "httpbin.ceshiren.com"

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

1、实战思路

2、自动化测试脚本思路

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

class TestPetStoreSearch:
    """
     宠物商店查询单接口测试
    """
    def setup_class(self):
        # 定义请求url
        self.base_url = "https://petstore.swagger.io/v2/pet"
        # 拼接宠物查询接口url
        self.search_url = self.base_url + "/findByStatus"

    def test_search_pet(self):
        # 宠物查询接口请求参数
        pet_status = {
            "status": "available"
        }
        # 发出请求
        r = requests.get(self.search_url, params=pet_status)
        #  打印接口响应信息
        print(r.text)
        # 状态断言
        assert r.status_code == 200
        # 业务断言,r.json()转换为python对象,不是空列表
        assert r.json() != []
        # 响应返回的是一个数组,数组中每一个对象都应该包含id字段
        assert "id" in r.json()[0]

日志配置:log_utils.py

# 配置日志
import logging
import 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)

代码优化

  • 新建日志配置。
  • 在用例中使用配置好的日志实例。
  • 使用 pytest parametrize 装饰器实现宠物状态的参数化。
  • 测试报告添加标题
"""
完成宠物商城宠物查询功能接口自动化测试。
编写自动化测试脚本。
完成断言。
"""
import allure
import pytest
import requests
from interface.utils.log_utils import logger


@allure.feature("宠物查询接口")
class TestPetStoreSearch:
    """
     宠物商店查询单接口测试
    """
    def setup_class(self):
        # 定义请求url
        self.base_url = "https://petstore.swagger.io/v2/pet"
        # 拼接宠物查询接口url
        self.search_url = self.base_url + "/findByStatus"

    # 正常场景用例
    # 参数化,第一个为需要传的参数,第二个为传的值,可以使用列表或元组,ids给用例命名
    @pytest.mark.parametrize(
        "status",
        ["available", "pending", "sold"],
        ids=["available_pets", "pending_pets", "sold_pets"]
                             )
    @allure.story("宠物查询接口冒烟用例")
    def test_search_pet(self, status):
        # 宠物查询接口请求参数
        pet_status = {
            "status": status
        }
        # 发出请求
        r = requests.get(self.search_url, params=pet_status)
        # 添加日志,打印接口响应信息
        logger.info(r.text)
        # 状态断言
        assert r.status_code == 200
        # 业务断言,r.json()转换为python对象,不是空列表
        assert r.json() != []
        # 响应返回的是一个数组,数组中每一个对象都应该包含id字段
        assert "id" in r.json()[0]

    # 异常场景,参数化
    @pytest.mark.parametrize(
        "status",
        ["petstatus", 123456, ""],
        ids=["wrong_value", "number", "none_str"]
    )
    @allure.story("status传入错误的值")
    def test_search_abnormal(self, status):
        """
        宠物查询接口异常场景
        :return:
        """
        # 宠物查询接口请求参数
        pet_status = {
            "status": status
        }
        # 发出请求
        r = requests.get(self.search_url, params=pet_status)
        # 添加日志,打印接口响应信息
        logger.info(r.text)
        # 状态断言
        assert r.status_code == 200
        # 业务断言,r.json()转换为python对象,不是空列表
        assert r.json() == []

    # 不传参
    @allure.story("不传status")
    def test_search_none_params(self):
        # 发出请求
        r = requests.get(self.search_url)
        # 添加日志,打印接口响应信息
        logger.info(r.text)
        # 状态断言
        assert r.status_code == 200
        # 业务断言,r.json()转换为python对象,不是空列表
        assert r.json() == []

    # 传非status的参数
    @allure.story("传入非status参数")
    def test_search_wrong_params(self):
        pet_status = {
            "key": "available "
        }
        # 发出请求
        r = requests.get(self.search_url, params=pet_status)
        # 添加日志,打印接口响应信息
        logger.info(r.text)
        # 状态断言
        assert r.status_code == 200
        # 业务断言,r.json()转换为python对象,不是空列表
        assert r.json() == []

生成测试报告

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