写点什么

软件测试 / 测试开发丨学习笔记之接口自动化测试

作者:测试人
  • 2023-05-26
    北京
  • 本文字数:6421 字

    阅读完需:约 21 分钟

获取更多相关知识

本文为霍格沃兹测试开发学社学员学习笔记分享,文末附原文链接

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

1、 接口测试场景


2、 自动化测试场景


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

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

4、 接口测试工具类型

5、 Requests

  • 是由 Python 实现的 API 测试框架。

  • 支持发起 POST, GET, PUT, DELETE 等请求。

  • 可以用来验证和校对响应信息。

  • 官网地址: requests.readthedocs.io/en/latest/

6、 Requests 优势

  • 功能全面:HTTP/HTTPS 支持全面。

  • 使用简单:简单易用,不用关心底层细节。

  • 定制性高:结合测试框架完成二次封装,比如 HttpRunner。


7、 Requests 环境准备

  • 安装命令:pip install requests

二、接口请求方法

1、 常见 HTTP 请求方法构造

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: 请求方法。 GET,OPTIONS,HEAD,POST,PUT,PATCH,DELETE。

  • url: 接口 url。

  • **kwargs:更多底层支持的参

8、 底层参数说明



三、接口请求参数

1、 请求参数简介

  • 接口请求中携带的,会在路径之后使用?代表客户端向服务端传递的参数。

  • 使用 key=value 形式拼接在 URL 中。

  • 如果有多个,则使用 &分隔

  • 例如:


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 请求消息中包含的元数据信息,用于描述请求或响应的一些属性和特征。

  • 实际工作过程中具体要关注的头信息字段需要和研发沟通

  • 常见的头信息(下面表格):

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、常用接口请求体



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、 接口断言使用场景

  • 问题: 如何确保请求可以发送成功。 如何保证符合业务需求。

  • 解决方案: 通过获取响应信息,验证接口请求是否成功,是否符合业务需求。

2、 Requests 中的响应结果对象

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

3、 响应结果类型

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"
复制代码

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

实战思路


自动化测试脚本思路


"""完成宠物商城宠物查询功能接口自动化测试。编写自动化测试脚本。完成断言。"""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 loggingimport 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 allureimport pytestimport requestsfrom 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() == []
复制代码


原文链接:https://ceshiren.com/t/topic/25120

发布于: 2023-05-26阅读数: 2
用户头像

测试人

关注

专注于软件测试开发 2022-08-29 加入

霍格沃兹测试开发学社,测试人社区:https://ceshiren.com/t/topic/22284

评论

发布
暂无评论
软件测试/测试开发丨学习笔记之接口自动化测试_程序员_测试人_InfoQ写作社区