接口测试与接口协议

一、接口测试

1.1 简介

  • 接口:接口可以叫做API(Application Programming Interface),本质上是后端的开发预先定义好的函数,这些函数可以提供一些确定的功能和服务。

    • 接口是在软件开发中,连接不同系统、软件或组件的关键点。它定义了通信方式和规范,协助组件之间有效地交互和写作。
  • 接口测试:是验证系统不同组件或模块之间的通信和数据交换的软件测试类型。

    • 它基于接口的输入和输出,测试每个接口在不同条件下的行为和功能。

    • 接口测试的目标是确保系统各组件之间的通信和数据交换准确、可靠。

  • 接口是前后端交互的桥梁。也就是前端和后端要进行数据交互时,都是需要通过接口的。

  • 这是接口测试的实际应用场景:通过接口对服务进行验证

1.2 应用场景

  • 集成测试:确保软件模块能够正确集成,并正常通信。

  • 版本迭代测试:验证新版本接口兼容性,保证旧版本功能正常。

  • 性能测试:评估接口在高负载情况下的性能表现。

  • 安全测试:检查接口安全性,确保数据传输安全。

  • 错误场景测试:测试异常情况下的接口行为,如错误输入数据、网络中断等。

  • 自动化测试:使用自动化脚本快速执行接口测试,提高效率。

1.3 价值

  1. 提高系统的可靠性
  • 接口测试可以确保不同模块或系统之间的数据交换和交互是正确和稳定的。
  • 通过测试接口,可以发现和修复在数据传输和处理过程中可能出现的问题,从而提高系统的可靠性。
  1. 早期发现问题
  • 接口测试通常在系统集成之前进行,能够在开发过程的早期发现问题。
  • 这有助于减少修复缺陷的成本和时间,因为越早发现问题,修复的代价越低。
  1. 验证系统的正确性
  • 接口测试验证系统各个模块之间的接口是否符合设计规范,确保数据传输和操作的正确性。
  • 这对于保证系统功能的整体正确性至关重要。
  1. 提高测试效率
  • 接口测试可以自动化,大大提高测试效率。
  • 与手动测试相比,自动化接口测试可以更快、更全面地覆盖各种测试场景和边界条件,减少人力和时间成本。
  1. 改善系统的可维护性
  • 通过接口测试,可以确保在对系统进行修改和更新时,不会引入新的缺陷。
  • 这提高了系统的可维护性,因为接口测试可以作为回归测试的一部分,确保系统的稳定性。
  1. 支持持续集成和持续交付
  • 接口测试是持续集成和持续交付流程中的重要组成部分。
  • 通过在每次代码提交后运行接口测试,可以快速反馈代码变更的影响,确保每次集成的稳定性和质量。
  1. 增强团队协作
  • 接口测试有助于不同团队(如开发团队和测试团队)之间的协作。
  • 通过清晰的接口定义和测试用例,各团队可以更好地理解和协作,减少沟通障碍和误解。
  1. 保证系统的可扩展性
  • 接口测试可以验证系统在不同负载和压力下的表现,确保系统的可扩展性。
  • 这对于大规模系统和高并发环境下的应用尤为重要。

1.4 分层测试体系

  • 金字塔模型,这个分层思想是马丁福勒提出的,他也是pageobject设计模式的奠基人。

  • 越往上,发现bug的时间越晚,成本越高;
  • 接口测试(Service)相比UI测试,可以更早发现问题,更快进行质量反馈。

五、常见的接口协议

2.1 简介

  • 服务于服务之间传递数据包,往往会因为不同的应用场景,使用不同的通讯协议进行传递。

    • 比如网站的访问,常常会使用HTTP协议;
    • 文件传输用FTP;
    • 邮件传递用SMTP。
  • 以上三种类型的协议都处于网络模型的应用层。

  • 除了应用层的常用协议外,对于传输层的TCP、UDP协议,以及Restful架构风格、RPC协议等等基础网络知识,要有一定的认知。

2.2 网络协议介绍

  • OSI参考模型是一个在制定协调进程间通信标准时,所使用的概念性框架,并不是一个标准。

  • TCP/IP四层模型是网际网络的基础通信架构,常视为是简化的七层OSI模型。

  • 五层协议是OSI和TCP/IP的综合,实际应用还是TCP/IP的四层架构。

  • TCP/IP协议栈是对应TCP/IP四层模型所使用的具体的网络协议。

2.3 TCP协议

  • TCP协议是在传输层中,一种面向连接的、可靠的、基于字节流的传输层通信协议。

  • TCP协议的工作方式,是在建立连接的时候需要“三次握手”,终止连接时需要“四次挥手”。

  • 适用场景:TCP协议的面向连接、错误重传、拥塞控制等特性,适用于高可靠性的场景,比如涉及用户信息的数据传输。

2.3 UDP协议

  • UDP协议一旦把应用程序发给网络层的数据发送出去,就不保留数据备份。

  • UDP仅在IP数据包的头部加入复用和数据校验字段,因此常常被认为是不可靠的数据包协议。

  • 适用场景:UDP无需提前建立连接、实现简单的特性,非常适用于实时性较高的场景,比如流媒体、在线游戏等。

2.4 RPC协议

  • RPC(Remote Procedure Call),以本地代码调用的方式,实现远程执行。

  • RPC主要用于公司内的服务调用。

  • RPC的的优点在于传输效率更高、性能损耗更低、自带负载均衡策略、更好的服务治理能力等。

常见的RPC协议

  • Dubbo:Java 上的高性能 RPC 协议,Apache 开源项目,由阿里捐赠 底层应用层协议支持 Dubbo 缺省 TCP 协议、HTTP、Hessian、Thrift、gRPC 等。

  • gRPC:高性能通用 RPC 框架,基于 Protocol Buffers。

  • PB(Protocol Buffers):是一个语言中立、平台中立的数据序列化框架。Google 开源项目。

  • Thrift:与 gRPC 类似的多语言 RPC 框架,Apache 开源项目。

  • JSON-RPC:A light weight remote procedure call protocol. It is designed to be simple。

2.5 HTTP协议

  • HTTP协议是接口测试中最常见的协议,是用于分布式、协作式和超媒体信息系统的应用层协议。

  • HTTP是万维网的数据通信的基础。客户端向服务器发送HTTP请求,服务端会在响应中返回所请求的数据。

  • 在测试过程中,常常需要校验请求和响应结果,因此了解HTTP协议,对于接口测试是至关重要的。

    • 请求:
      • 请求行:method、url、protocal
      • 请求方法:GET、POST、PUT、DELETE、HEAD
      • 请求头:Host、Cookie、User-Agent
      • 请求参数:query
      • 请求体:JSON、XML、FORM
    • 响应:
      • 响应状态行
      • 响应头
      • 响应体

2.5.1 URI和URL

  • URI:统一资源标识符,用来唯一标识的一个资源;
  • URL:统一资源定位符,是一种具体的URI。
    • URL结构:https://www.baidu.com/s?wd=霍格沃兹&rsv_spt=1
      1. 协议:http
      2. 域名:www.baidu.com
      3. 端口:跟在域名后面,域名和端口之间使用“:”作为分隔符,非必须,如果省略端口部分,将采用默认端口。http默认端口是80,https默认端口是443。
      4. 路径:/s
      5. 请求参数:wd=霍格沃兹&rsv_spt=1

2.5.2 请求报文

> GET /uploads/user/avatar/31438/8216a3.jpg HTTP/1.1
> Host: ceshiren.com
> Accept-Encoding: deflate, gzip
> Connection: keep-alive
> Pragma: no-cache
> Cache-Control: no-cache
> User-Agent: Mozilla/5.0 
(Macintosh; Intel Mac OS X 10_15_0)
 AppleWebKit/537.36 
 (KHTML, like Gecko) 
 Chrome/80.0.3987.116 Safari/537.36
> Accept: image/webp,image/apng,image/*,*/*;q=0.8
> Referer: https://ceshiren.com/
> Accept-Language: en,zh-CN;q=0.9,zh;q=0.8
> Cookie: user_id=xx;

_homeland_session=xx;
>

2.5.3 HTTP响应报文

< HTTP/1.1 200 OK
< Server: nginx/1.10.2
< Date: Thu, 12 Mar 2020 09:13:44 GMT
< Content-Type: image/png
< Content-Length: 11390
< Connection: keep-alive

2.5.4 HTTP响应状态码

状态码 说明 举例
1xx 临时响应,表示通知信息
请求收到了或正在进行处理
100:客户端应继续其请求
101:服务器根据客户端的请求切换协议
2xx 表示成功、接受或知道了 200:请求成功
201:请求成功并且服务器创建了新的资源
202:服务器已接受请求,但尚未处理
204:服务器成功处理了请求,但没有返回内容
3xx 表示重定向 301:请求的资源已被永久移动到新位置
302:请求的资源暂时从不同的URL响应请求
304:资源未被修改,可以使用缓存的版本
4xx 客户端错误状态码 400:由于语法错误,服务器无法理解请求
401:请求要求身份验证
403:服务器拒绝请求
404:服务器找不到请求的资源
405:请求方法不允许
5xx 服务器错误状态码 500:服务器遇到错误,无法完成请求
501:服务器不支持请求的方法
502:服务器作为网关或代理,从上游服务器收到无效响应
503:服务器暂时过载或维护,无法处理请求
504:服务器作为网关或代理,未能及时从上游服务器接受请求

2.6 RESTful架构

2.6.1 简介

  • RESTful框架是一种用来创建基于REST(Representational State Transfer,表现层状态转移)架构风格的Web服务框架。

  • RESTful架构是一种通过网络协议(通常是HTTP)进行通信的架构风格,主要用于构建可扩展的、简洁的、面向资源的服务。

  • 特点:

    • 面向资源:资源是网络上可识别的任何内容,如文档、图像、数据等;每个资源都有一个唯一的URI。

    • 使用标准的HTTP方法:常用的HTTP方法包括GET、POST、PUT、DELETE,对应资源的读取、创建、更新、删除等操作。

    • 无状态性:每个请求都是独立的,服务器不存储客户端的状态。所有的信息都包含在请求中。

    • 统一接口:通过标准化的接口,客户端和服务器之间的交互变得简单明了。常用的方法包括URI、HTTP方法、状态码、媒体类型等。

    • 表现层表示:服务端向客户端发送资源的表示,如JSON、XML等格式。

    • 可缓存性:服务器响应应该指明是否可以缓存,以提高性能。

2.6.2 HTTP基本行为代表意义

行为 说明
HEAD 可针对任何资源发出请求,以仅获取HTTP头信息
GET 用于检索资源
POST 用于创建资源
PATCH 用于使用部分JSON数据更新资源。
例如,一个Issue资源有title和body属性。
PATCH请求可以接受一个或多个属性来更新资源。
PATCH是一个相对较新且不常用的HTTP动词,因此资源端点也接受POST请求。
PUT 用于替换资源或集合。
对于没有body属性的PUT请求,需确保将Content-Length头设置为0。
DELETE 用于删除资源。

2.6.3 GET与POST的区别

  • 日常工作中,最常用的HTTP请求是GET和POST,它们的区别在于:
    • 请求方法不同;

    • POST可以附加body,可以支持Form、JSON、XML、Binary等各种数据格式;

    • 从行业通用规范看,如果对数据库不会产生变化的,比如查询操作,建议使用GET;数据的写入与状态建议使用POST。

2.6.4 RESTful框架的使用

  1. 选择合适的RESTful框架:
  • 不同编程语言有不同的RESTful框架。例如:
    • JavaScript:Spring Boot

    • Python:Flask、Django Rest Framework

    • JavaScript:Express.js(Node.js)

    • PHP:Laravel

  1. 定义资源和URI
  • 确定系统中的资源,并为每个资源分配唯一的URI。
  1. 实现资源的CRUD操作
  • 使用框架提供的功能,实现资源的创建、读取、更新、删除等操作。例如,在Flask中可以使用路由和视图函数,来处理不同的HTTP请求。
  1. 处理请求和响应
  • 解析客户端的请求,处理数据逻辑,并生成响应的响应。响应通常包含状态码、头信息和资源的表示。
  1. 测试和部署
  • 使用工具(如Postman)测试API,确保其正确性。部署服务到生产环境中。

示例:

from flask import Flask, request, jsonify

# 创建Flask应用
app = Flask(__name__)

# 示例数据,存储任务的列表
tasks = [
    {'id': 1, 'title': 'Learn REST', 'description': 'Understand RESTful principles', 'done': False},
    {'id': 2, 'title': 'Build API', 'description': 'Create a RESTful API using Flask', 'done': False}
]

# 路由:获取所有任务
@app.route('/tasks', methods=['GET'])  # 路由:当客户端发送GET请求到'/tasks'时,会调用get_tasks函数
def get_tasks():  # 视图:定义处理请求的函数
    return jsonify({'tasks': tasks})  # 返回所有任务的JSON表示

# 路由:获取单个任务
@app.route('/tasks/<int:task_id>', methods=['GET'])  # 路由:当客户端发送GET请求到'/tasks/<task_id>'时,会调用get_task函数
def get_task(task_id):  # 视图:定义处理请求的函数,接受任务ID作为参数
    task = next((task for task in tasks if task['id'] == task_id), None)  # 查找任务ID匹配的任务
    if task is None:  # 如果任务未找到
        return jsonify({'error': 'Task not found'}), 404  # 返回错误信息和404状态码
    return jsonify({'task': task})  # 返回找到的任务的JSON表示

# 路由:创建新任务
@app.route('/tasks', methods=['POST'])  # 路由:当客户端发送POST请求到'/tasks'时,会调用create_task函数
def create_task():  # 视图:定义处理请求的函数
    if not request.json or not 'title' in request.json:  # 检查请求是否为JSON并且包含'title'字段
        return jsonify({'error': 'Bad request'}), 400  # 如果请求无效,返回错误信息和400状态码
    task = {
        'id': tasks[-1]['id'] + 1,  # 生成新任务ID,为当前列表中最后一个任务ID加1
        'title': request.json['title'],  # 从请求中获取'title'字段
        'description': request.json.get('description', ''),  # 从请求中获取'description'字段,默认为空字符串
        'done': False  # 默认任务未完成
    }
    tasks.append(task)  # 将新任务添加到任务列表中
    return jsonify({'task': task}), 201  # 返回新创建的任务的JSON表示和201状态码

# 路由:更新任务
@app.route('/tasks/<int:task_id>', methods=['PUT'])  # 路由:当客户端发送PUT请求到'/tasks/<task_id>'时,会调用update_task函数
def update_task(task_id):  # 视图:定义处理请求的函数,接受任务ID作为参数
    task = next((task for task in tasks if task['id'] == task_id), None)  # 查找任务ID匹配的任务
    if task is None:  # 如果任务未找到
        return jsonify({'error': 'Task not found'}), 404  # 返回错误信息和404状态码
    if not request.json:  # 检查请求是否为JSON
        return jsonify({'error': 'Bad request'}), 400  # 如果请求无效,返回错误信息和400状态码
    task['title'] = request.json.get('title', task['title'])  # 更新任务的'title'字段
    task['description'] = request.json.get('description', task['description'])  # 更新任务的'description'字段
    task['done'] = request.json.get('done', task['done'])  # 更新任务的'done'字段
    return jsonify({'task': task})  # 返回更新后的任务的JSON表示

# 路由:删除任务
@app.route('/tasks/<int:task_id>', methods=['DELETE'])  # 路由:当客户端发送DELETE请求到'/tasks/<task_id>'时,会调用delete_task函数
def delete_task(task_id):  # 视图:定义处理请求的函数,接受任务ID作为参数
    global tasks  # 使用全局变量tasks
    tasks = [task for task in tasks if task['id'] != task_id]  # 从任务列表中删除匹配的任务
    return jsonify({'result': True})  # 返回删除结果的JSON表示

# 主程序入口,启用调试模式运行Flask应用
if __name__ == '__main__':
    app.run(debug=True)  # 运行Flask应用,启用调试模式