接口自动化之断言

一、接口响应断言

(一)接口断言使用场景

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

Requests 中的响应结果对象

import requests
from requests import Response

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

响应结果类型

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

def test_res_assert():
    # 定义接口的 url 和 json 格式请求体
    url = "https://httpbin.ceshiren.com/get"
    # 发出 GET 请求,r 接收接口响应
    r = requests.post(url)

响应状态码断言

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

def test_req():
    r = requests.get("https://httpbin.ceshiren.com/get")
    assert r.status_code == 200

二、JSON 响应体断言

1、什么是 JSON 响应体

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

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

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


3、断言 JSON 格式响应体

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

def test_res_json():
    r = requests.get("https://httpbin.ceshiren.com/get")
    assert r.status_code == 200
    assert r.json()["url"] == "https://httpbin.ceshiren.com/get"

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

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

三、整体结构响应断言

1、响应信息数据极为庞大

https://ceshiren.com/t/topic/16658.json

2、针对于“大响应数据”如何断言

  • 针对主要且少量的业务字段断言。
  • 其他字段不做数据正确性断言,只做类型与整体结构的校验。
  • 与前面的版本进行 diff,对比差异化的地方。

3、JSONSchema 简介

  • 使用 JSON 格式编写的
  • 可以用来定义校验 JSON 数据的结构
  • 可以用来校验 JSON 数据的一致性
  • 可以用来校验 API 接口请求和响应

4、JSONSchema 整体结构响应断言

  1. 预先生成对应结构的 Schema。
  2. 将实际获取到的响应与生成的 Schema 进行对比。

5、JSONSchema 的生成

  • 通过界面工具生成。
  • 通过第三方库生成。
  • 通过命令行工具生成。

6、JSONSchema 的生成效果

// # 预期的 JSON 文档结构
{
  "name": "Hogwarts",
  "Courses": ["Mock", "Docker"]
}
// jsonschema
{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "$ref": "#/definitions/Welcome",
  "definitions": {
    "Welcome": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "name": {
          "type": "string"
        },
        "Courses": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": ["Courses", "name"],
      "title": "Welcome"
    }
  }
}

7、界面工具生成

  • 复制 JSON 数据
  • 粘贴到在线生成工具中
  • 自动生成 JSON Schema 数据

JSON Schema 在线生成工具:https://app.quicktype.io

8、第三方库生成(Python)

  1. 安装:pip install genson
  2. 调用方法生成对应的 JSONSchema 数据结构。
from genson import SchemaBuilder
def generate_jsonschema(obj):
    # 实例化jsonschem
    builder = SchemaBuilder()
    # 传入被转换的对象 
    builder.add_object(obj)
    # 转换成 schema 数据
    return builder.to_schema()

9、JSONSchema 验证(Python)

  1. 安装:pip install jsonschema
  2. 调用 validate() 进行验证。
def schema_validate(obj, schema):
    '''
    对比 python 对象与生成的 JSONSchame 的结构是否一致
    '''
    try:
        validate(instance=obj, schema=schema)
        return True
    except Exception as e:
        return False

10、JSONSchema 二次封装

  • 生成JSONSchema
  • 验证JSONSchema
class JSONSchemaUtils:
    @classmethod
    def generate_schema(cls, obj):
        # 实例化jsonschem
        builder = SchemaBuilder()
        # 传入被转换的对象 
        builder.add_object(obj)
        # 转换成 schema 数据
        return builder.to_schema()

    @classmethod
    def schema_validate(cls, obj, schema):
        '''
        对比 python 对象与生成的 json schame 的结构是否一致
        '''
        try:
            validate(instance=obj, schema=schema)
            return True
        except Exception as e:
            return False

四、数据库操作与断言

1、接口测试响应验证

如何在测试过程中验证接口没有 Bug?

  1. 通过接口响应值
  2. 通过查询数据库信息辅助验证

2、接口测试数据清理

自动化测试过程中,会产生大量的脏数据,如何处理?

  • 通过 Delete 接口删除
  • 自动化测试使用干净的测试环境,每次自动化测试执行完成之前或之后做数据还原。

3、数据库操作注意事项

直接对数据库做查询之外的操作是非常危险的行为

  • 权限管理严格的公司数据库权限给的非常低
  • 表结构复杂,随便删除数据会影响测试,甚至会导致系统出现异常

4、接口自动化测试常用的数据库操作

  • 连接与配置
  • 查询数据与断言

Python技术栈:章节《常用第三方库pymsql》;Java技术栈:《常用标准库:数据库操作-JDBC》

实战目标

  • 第一次全流程实战的断言通过数据库验证

数据库信息

  • 主机: litemall.hogwarts.ceshiren.com
  • 端口: 13306
  • 用户名: test
  • 密码: test123456

注意:只有查询权限

数据库封装(Python)

  • 封装数据库配置
  • 封装 sql 查询操作
  • 调用方法执行 sql 语句
import pymysql

# 封装建立连接的对象
def get_conn():
    conn = pymysql.connect(
        host="litemall.hogwarts.ceshiren.com",
        port=13306,
        user="test",
        password="test123456",
        database="litemall",
        charset="utf8mb4"
    )
    return conn
# 执行sql语句
def execute_sql(sql):
    connect = get_conn()
    cursor = connect.cursor()
    cursor.execute(sql)  # 执行SQL
    record = cursor.fetchone()  # 查询记录
    return record

if __name__ == '__main__':
    # 执行sql语句查询user123这个用户的购物车有一个名称为 hogwarts1 的商品
    execute_sql("select * from litemall_cart where "
                "user_id=1 and deleted=0 and "
                "goods_name='hogwarts1'")

查询数据与数据库断言(Python)

  • 查询数据,添加查询条件
  • 断言结果不为 None
# 查询查询user123这个用户的购物车有一个名称为 hogwarts1 的商品
sql_res = execute_sql("select * from litemall_cart where "
            "user_id=1 and deleted=0 and "
            "goods_name='hogwarts1'")

assert sql_res

五、接口鉴权的多种情况与解决方案

1、接口鉴权是什么

  • 身份认证


    image.png

    image.png

接口鉴权通用的解决方案

  • 认证信息的获取
  • 认证信息的携带
@startuml
scale 800
if (登录成功?) then
  #pink:响应错误;
  detach
endif
#palegreen:响应认证信息;
#palegreen:携带认证信息发起其他请求;
@enduml

后端接口鉴权常用方法

@startmindmap
* 常用方式
** cookie
*** 1\. 携带身份信息请求认证
*** 2\. 之后的每次请求都携带cookie信息,cookie记录在请求头中
** token
*** 1\. 携带身份信息请求认证
*** 2\. 之后的每次请求都携带token认证信息
*** 3\. 可能记录在请求头,可能记录在url参数中
** auth
*** 每次请求携带用户的username和password,并对其信息加密
** oauth2(选修)
*** 1\. 携带身份信息请求认证
*** 2\. 服务端向指定回调地址回传code
*** 3\. 通过code获取token
*** 4\. 之后的请求信息都携带token。
*** 典型产品 微信自动化测试
@endmindmap

cookie 鉴权

  1. cookie 的获取(根据接口文档获取)
  2. 发送携带 cookie 的请求
    • 直接通过 cookies 参数
    • 通过 Session() 对象
import requests

class TestVerify:
    def setup_class(self):
        self.proxy = {"http": "http://127.0.0.1:8080",
                      "https": "http://127.0.0.1:8080"}

    def test_cookies_by_write(self):
        # 简单场景,直接写入cookie
        url = "https://httpbin.ceshiren.com/cookies"
        requests.get(url, proxies=self.proxy, verify=False, cookies={"hogwarts": "ad"})

    def test_cookies(self):
        # 获取session 的实例,需要通过Session()保持会话,
        # 即为认证之后,之后所有的实例都会携带cookie
        # 可以模仿用户在浏览器的操作
        req = requests.Session()
        # 第一次登陆,植入cookie
        set_url = "https://httpbin.ceshiren.com/cookies/set/hogwarts/ad"
        req.get(set_url, proxies=self.proxy, verify=False)
        # 第二次请求的时候即可携带cookie信息
        url = "https://httpbin.ceshiren.com/cookies"
        req.get(url, proxies=self.proxy, verify=False)

token 鉴权

  1. token 的获取(根据接口文档获取)
  2. 发送携带 token 的请求(根据接口文档获取)
class TestVerify:
    def setup_class(self):
        self.proxy = {"http": "http://127.0.0.1:8080",
                      "https": "http://127.0.0.1:8080"}

    def test_token(self):
        # 1\. 获取token
        url = "http://litemall.hogwarts.ceshiren.com/admin/auth/login"
        user_data = {"username": "admin123", "password": "admin123", "code": ""}
        r = requests.post(url, json=user_data, proxies=self.proxy, verify=False, )
        self.token = r.json()["data"]["token"]
        # 2\. 之后的请求均携带token
        goods_list_url = "http://litemall.hogwarts.ceshiren.com/admin/goods/list"
        goods_data = {"name": "hogwarts", "order": "desc", "sort": "add_time"}
        r = requests.get(goods_list_url, params=goods_data,
                         headers={"X-Litemall-Admin-Token": self.token},
                         proxies=self.proxy, verify=False)

auth 鉴权(了解即可)

  • 在基本 HTTP 身份验证中,请求包含格式为 的标头字段Authorization: Basic<credentials style="box-sizing: inherit;"></credentials>
  • 其中credentials是 ID 和密码的Base64编码,由单个冒号连接:。

[图片上传失败...(image-7c678-1694090124455)]

auth 鉴权-代码示例

import requests
from requests.auth import HTTPBasicAuth

class TestVerify:
    def setup_class(self):
        self.proxy = {"http": "http://127.0.0.1:8080",
                      "https": "http://127.0.0.1:8080"}
    def test_basic_auth(self):
        # 表示访问一个需要BasicAuth认证的路径
        # username=用户名,password=密码
        # 如果不使用basic auth 则会失败
        r = requests.get("https://httpbin.ceshiren.com/basic-auth/username/password",
                         proxies=self.proxy, verify=False,
                         auth=HTTPBasicAuth("username", "password"))
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容