接口自动化之断言

一、接口响应断言

(一)接口断言使用场景

  • 问题:
    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"))
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容