当前位置: 首页 > news >正文

【博客系统测试报告】---接口自动化测试

目录

1、需求分析

2、挑选接口

3、设计博客系统的测试用例

4、设计自动化测试框架

test_add.py:

test_detail.py:

test_getAuthorInfo.py:

test_getUserInfo:

test_list.py:

test_login.py:

logger_util.py:

request_util.py:

yaml_util.py:


1、需求分析

根据业务需求,明确接口需要实现的具体功能,如数据的获取、修改、删除等操作,以及接口的输入输出要求。分析接口直接的依赖关系,确定接口的调用顺序和依赖条件。

2、挑选接口

优先选择核心业务接口、频繁使用的接口以及容易出错的接口进行自动化测试。

功能复杂度高、高风险功能、重复性高

我们对博客系统的每个接口都进行自动化测试

1、登录接口:

2、博客列表页

3、编辑博客页

4、获取用户详情

5、获取博客详情

6、获取登录用户信息

3、设计博客系统的测试用例

请求方法、请求参数、需要用户凭证就可以分为登录状态和未登录状态

针对接口设计测试用例,必须要按照接口文档来进行设计,除此之外,最好能够看到接口对应的代码,查看接口存在的不同响应,针对不同的响应来设计测试用例。

4、设计自动化测试框架

语言选择:Python

技术栈:pytest框架、request模块、PyYAML模块、jsonschema模块、allure-pytest模块、logging模块

集成开发环境:pycharm

如果下一个项目要用和上一个相同的配置的话,可以在上一个项目中创建一个txt文件,将需要安装的框架和模块写进入,然后将此文件在文件夹中复制到新的项目的文件夹里。

再使用就可以将框架和模块配置好

pip install -r 文件名

我们对每个接口都设计一个测试文件,但是要注意文件名要符合pytest的收集规则。把通用的方法进行封装

整个项目架构

test_add.py:

'''
添加博客接口
'''
import pytest
from jsonschema import validatefrom utils.request_util import host, Request
from utils.yaml_util import read_yamlclass TestAdd:url = host + "blog/add"schema = {"type": "object","required": ["code","errMsg","data"],"additionalProperties": False,"properties": {"code": {"type": "string"},"errMsg": {"type": "string"},"data": {"type": "boolean"}}}#未登录状态下请求add接口 状态码都是401def test_add_noLogin(self):r = Request().post(url=self.url)r.status_code == 401#添加博客--添加成功+添加失败@pytest.mark.parametrize("add",[#添加成功{"title": "接口自动化标题","content": "接口自动化内容","data" : True #在参数化中不仅可以配置请求参数,响应数据中想要校验的关键字段也可以在这配置},#标题为空{"title": "","content": "接口自动化内容","data": False},#内容为空{"title": "接口自动化标题","content": "","data": False},#标题和内容都为空{"title": "","content": "","data": False}])def test_add(self,add):#请求头token = read_yaml("data.yml","user_token_header")header = {"user_token_header":token}#请求参数json = {"title":add["title"],"content":add["content"]}#发送请求r = Request().get(url=self.url,headers=header,json=json)#验证jsonschemavalidate(instance=r.json(),schema=self.schema)#instance是接口的返回值#assert关键字段的值assert r.json()['data'] == add['data']

test_detail.py:

from jsonschema import validate
import pytest
from utils.request_util import host, Request
from utils.yaml_util import read_yaml#博客详情页需要有效的blogId,这个id必须要从博客列表页的返回值里面的id来获取
class TestDetail:url = host + "blog/getBlogDetail" #注意blog前面不要加/schema = {"type": "object","additionalProperties": False,"required": [ "code","errMsg","data"],"properties": {"code": {"type": "string"},"errMsg": {"type": "string"},"data": {"type": "object","required": ["id","title","content","userId","deleteFlag","createTime","updateTime","loginUser"],"additionalProperties": False,"properties": {"id": {"type": "number"},"title": {"type": "string"},"content": {"type": "string"},"userId": {"type": "number"},"deleteFlag": {"type": "number"},"createTime": {"type": "string"},"updateTime": {"type": "string"},"loginUser": {"type": "boolean"}}}}}#未登录状态下访问博客详情页def test_detail_noLogin(self):url = self.url + "?blogId=1234"r = Request().get(url=url)assert r.status_code == 401#登录状态下def test_detail_login(self):url = self.url + "?blogId=" + str(read_yaml("data.yml","blogId"))token = read_yaml("data.yml","user_token_header")header = {"user_token_header":token}r = Request().get(url=url,headers=header)#先通过jsonschema进行校验validate(instance=r.json(),schema=self.schema)#再进行assert关键字段的校验assert r.json()["code"] == "SUCCESS"#博客详情页---blogId错误#为空、不存在的、非数字、负数、过长的数字@pytest.mark.parametrize("blogId",["",1234,"nihao",-100,99999999999999999999999999999])def test_detail_fail(self,blogId):# url = self.url + blogId  url本身就是字符串 要拼接blogId必须也是字符串的格式url = self.url#配置参数#request发送请求,参数有三种格式#params:一般是发送get请求可以拼接再url上#data:一般是发送一个post格式的表单的请求#json:发送post方法json格式的参数params = {"blogId":blogId}token = read_yaml("data.yml", "user_token_header")header = {"user_token_header": token}r = Request().get(url=url, headers=header)expect_json = {"code": "FAIL","errMsg": "内部错误, 请联系管理员","data": None}assert r.json() == expect_json

test_getAuthorInfo.py:

from jsonschema import validate
import pytest
from utils.request_util import host, Request
from utils.yaml_util import read_yamlclass TestgetAuthorInfo:url = host + "user/getAuthorInfo"schema = {"type": "object","required": ["code","errMsg","data"],"additionalProperties": False,"properties": {"code": {"type": "string"},"errMsg": {"type": "string"},"data": {"type": ["object","null"],"required": ["id","userName","password","githubUrl","deleteFlag","createTime","updateTime"],"additionalProperties": False,"properties": {"id": {"type": "number"},"userName": {"type": "string"},"password": {"type": "string"},"githubUrl": {"type": "string"},"deleteFlag": {"type": "number"},"createTime": {"type": "string"},"updateTime": {"type": "string"}}}}}#未登录状态下访问接口def test_etAuthorInfo_nologin(self):url = self.url + "?blogId=162202"r = Request().get(url=url)assert r.status_code == 401#登录状态下正确请求#有效的blogIddef test_etAuthorInfo(self):blogId = read_yaml("data.yml","blogId")url = self.url + "?blogId=" + str(blogId)#读取用户登录凭证token = read_yaml("data.yml","user_token_header")header = {"user_token_header" : token}#发起请求r = Request().get(url=url,headers = header)#校验jsonschemavalidate(instance=r.json(),schema=self.schema)assert r.json()['code'] == "SUCCESS"#登录状态下异常请求#blogId异常@pytest.mark.parametrize("blogId,expected_code", [("","FAIL"),(1234,"FAIL"),("nihao","FAIL"),(-100,"SUCCESS"),(99999999999999999999999999999,"FAIL")])def test_etAuthorInfo_fail(self,blogId,expected_code):url = self.url#把blogId通过参数进行配置params = {"blogId":blogId}# 读取用户登录凭证token = read_yaml("data.yml", "user_token_header")header = {"user_token_header": token}# 发起请求r = Request().get(url=url, headers=header,params = params)validate(instance=r.json(),schema=self.schema)assert r.json()["code"] == expected_code

test_getUserInfo:

from jsonschema import validatefrom utils.request_util import host, Request
from utils.yaml_util import read_yamlclass TestgetUserInfo:url = host + "user/getUserInfo"schema = {"type": "object","required": ["code","errMsg","data"],"properties": {"code": {"type": "string"},"errMsg": {"type": "string"},"data": {"type": "object","required": ["id","userName","password","githubUrl","deleteFlag","createTime","updateTime"],"properties": {"id": {"type": "number"},"userName": {"type": "string"},"password": {"type": "string"},"githubUrl": {"type": "string"},"deleteFlag": {"type": "number"},"createTime": {"type": "string"},"updateTime": {"type": "string"}}}}}#未登录状态下请求接口def test_getUserInfo_nologin(self):r = Request().get(url = self.url)assert r.status_code == 401#登录状态下请求接口def test_getUserInfo_login(self):token = read_yaml("data.yml","user_token_header")header = {"user_token_header" : token}r = Request().get(url = self.url,headers = header)#校验jsonschemavalidate(instance=r.json(),schema=self.schema)#通过断言校验关键数据assert r.json()["code"] == "SUCCESS"

test_list.py:

from jsonschema import validate
import pytest
from utils.request_util import host, Request
from utils.yaml_util import read_yaml, write_yaml@pytest.mark.order(2)
class TestList:url = host + "blog/getList"schema = {"type": "object","required": ["code","errMsg","data"],"additionalProperties": False,"properties": {"code": {"type": "string"},"errMsg": {"type": "string"},"data": {"type": "array","items": {"type": "object","required": ["id","title","content","userId","deleteFlag","createTime","updateTime","loginUser"],"additionalProperties": False,"properties": {"id": {"type": "number"},"title": {"type": "string"},"content": {"type": "string"},"userId": {"type": "number"},"deleteFlag": {"type": "number"},"createTime": {"type": "string"},"updateTime": {"type": "string"},"loginUser": {"type": "boolean"}}}}}}#未登录状态def test_list_nologin(self):r = Request().get(url=self.url)assert r.status_code == 401#请求列表页---登录状态def test_list_login(self):token = read_yaml("data.yml","user_token_header")header = {"user_token_header":token#从yaml文件中读取数据}r = Request().get(url=self.url,headers = header)#jsonschema校验validate(instance=r.json(),schema=self.schema) #将http响应体的内容解析为json格式 第一个参数和第二个参数进行对比#关键值的校验assert r.json()['code'] == "SUCCESS"#提权有效的blogid存储在yaml文件中blogId = {"blogId":r.json()['data'][0]["id"]}write_yaml("data.yml",blogId)

test_login.py:

'''
登录--接口自动化测试
url:http://8.137.19.140:9090/user/login   POST
form-data{"username":"zhangsan","password":"123456"}
'''
import re
from lib2to3.pygram import python_grammar_no_print_and_exec_statementfrom jsonschema import validate
from utils.request_util import host,Requestimport pytestfrom utils.yaml_util import write_yaml#参数化+jsonschema校验+关键字段的严格校验@pytest.mark.order(1)
class TestLogin:url = host + "user/login"schema = {  #schema是固定的所以可以定义成全局的"type": "object","required": ["code","errMsg","data"],"additionalProperties": False,   #不能有额外的属性"properties": {"code": {"type": "string"},"errMsg": {"type": "string"},"data": {"type": ["string","null"]}}}
#我们将异常登录放在正常登录之前
#只为了避免正常登录之后,再进行异常登录,导致登录态被清空# 异常登录@pytest.mark.parametrize("login", [# 错误的账号和密码{"username": "zhang","password": "123","errMsg" : "用户不存在"},#错误的账号和正确的密码{"username": "zhang","password": "123456","errMsg": "用户不存在"},#正确的账号和错误的密码{"username": "zhangsan","password": "123","errMsg": "密码错误"},#不存在的账号{"username": "hhh","password": "333","errMsg": "用户不存在"},#账号和密码都为空{"username": "","password": "","errMsg": "账号或密码不能为空"},#过长的账号{"username": "zhehegdhiiwbcicbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","password": "123456","errMsg": "用户不存在"},#过长的密码{"username": "zhangsan","password": "22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222","errMsg": "密码错误"}])def test_login_fail(self,login):data  = {"username": login["username"],"password": login["password"]}r = Request().post(url=self.url, data=data)validate(instance=r.json(), schema=self.schema)assert r.json()["code"] == "FAIL"#因为用户会有很多,因此需要使用参数化@pytest.mark.parametrize("login",[{"username":"zhangsan","password":"123456",},{"username":"lisi","password":"123456"}])#成功请求登录接口def test_login_success(self,login):data = {"username": login["username"],"password": login["password"]}r = Request().post(url=self.url,data = data)#校验#返回的字段类型是否正确#我们可以进一步进行限制 code和data的值肯定是 SUCCESSvalidate(instance=r.json(),schema=self.schema)#断言返回的接口assert r.json()["code"] == "SUCCESS"assert re.match('\S{100,}',r.json()['data'])#匹配的是返回值里面的data数据#接口返回的data就是其他接口的登录凭证#将用户的登录凭证保存在yaml文件中token = {"user_token_header" : r.json()['data']}write_yaml("data.yml",token) #把接口返回的数据保存在yaml文件中

logger_util.py:

将日志进行分割

2026-01-01.log

2026-01-01-info.log

2026-01-01-err.log

#打印日志,输出到日志文件中
import logging
import os.path
import time#继承父类的logging.Filter 并重写父类的方法
class infoFilter(logging.Filter):def filter(self, record):return record.levelno == logging.INFO
class errFilter(logging.Filter):def filter(self, record):return record.levelno == logging.ERRORclass logger:#1、获取日志对象---定义类方法@classmethod@classmethoddef getlog(cls): #注意!!!cls.logger = logging.getLogger(__name__) #创建一个日志对象  再给一个模块名cls.logger.setLevel(logging.DEBUG)#将日志输出到日志文件中 --- 希望将日志进行分割,按照日期分开保存,避免日志文件过大不好管理#---将日志按照级别进行分割# 保证logs文件夹存在 必须创建好了才能往里面保存日志文件LOG_PATH = "../logs/"  #当前项目路径下创建一个文件夹if not os.path.exists(LOG_PATH):  # 判断这个路径是否存在os.mkdir(LOG_PATH)  # 如果文件夹不存在就去创建文件夹'''logs./logs/2026-01-01.log./logs/2026-01-01-info.log./logs/2026-01-01-err.log'''now = time.strftime("%Y-%m-%d")log_name = LOG_PATH + now + ".log"info_log_name = LOG_PATH + now + "-info.log"err_log_name = LOG_PATH + now + "-err.log"#2、创建文件处理器all_handler = logging.FileHandler(log_name,encoding="utf-8")info_handler = logging.FileHandler(info_log_name,encoding="utf-8")err_handler = logging.FileHandler(err_log_name, encoding="utf-8")#3、设置日志格式formatter = logging.Formatter("%(asctime)s %(levelname)s [%(name)s] [%(filename)s (%(funcName)s:%(lineno)d)] - %(message)s")all_handler.setFormatter(formatter)info_handler.setFormatter(formatter)err_handler.setFormatter(formatter)#添加文件过滤器info_handler.addFilter(infoFilter())err_handler.addFilter(errFilter())#4、将文件处理器添加到日志记录器中cls.logger.addHandler(all_handler)cls.logger.addHandler(info_handler)cls.logger.addHandler(err_handler)return cls.logger #返回类方法里面的logger对象

request_util.py:

#封装请求方法,是get还是post
import requests
from utils.logger_util import loggerhost = "http://8.137.19.140:9090/" #这些都是重复的 设置为全局变量class Request:log = logger.getlog() #通过类来获得日志对象def get(self,url,**kwargs):self.log.info("准备发起get请求,url:"+url)self.log.info("接口信息:{}".format(kwargs))r = requests.get(url=url,**kwargs)self.log.info("接口响应状态码:{}".format(r.status_code))self.log.info("接口响应内容:{}".format(r.text))return rdef post(self, url, **kwargs):self.log.info("准备发起post请求,url:" + url)self.log.info("接口信息:{}".format(kwargs))r = requests.post(url=url, **kwargs)self.log.info("接口响应状态码:{}".format(r.status_code))self.log.info("接口响应内容:{}".format(r.text))return r

yaml_util.py:

#数据也需要存储在yaml文件里面,从yaml文件中读取数据、存储以及清理的工作
'''
yaml相关操作
'''
import osimport yaml#往yaml文件中写入数据
def write_yaml(filename,data):with open(os.getcwd()+"/data/"+filename,mode="a+",encoding="utf-8") as f:#获取当前项目的路径yaml.safe_dump(data,stream=f) #往文件里写
#读取数据
def read_yaml(filename,key): #yaml文件是json格式,我们通过key就可以把值读出来with open(os.getcwd()+"/data/"+filename,mode="r",encoding="utf-8") as f:data = yaml.safe_load(f) #返回所有的json数据return data[key]  #只返回我要查找的数据
#清空
def clear_yaml(filename):with open(os.getcwd()+"/data/"+filename,mode="r",encoding="utf-8") as f:f.truncate()

指定测试用例运行的顺序:

pip install pytest-order==1.3.0

现在有很多博客,现在我们进行删除博客操作之后,再进行执行测试用例就会出现报错,因为我们获得的blogId已经失效了,因此我们要指定文件的执行顺序

必须保证这两个的顺序:

登录接口测试用例---获取用户登录凭证再yaml文件中

列表页接口测试用例 --- 获取有效blogId保存在yaml文件中

这个可以作用在测试类上,也可以作用在测试方法上

执行测试:

测试报告:

测试时间

测试时间与测试⽤例数量成正⽐。⽤例数量越多,测试时间越⻓。如果时间太长需要通过优化测试脚本、并⾏执⾏和分布式测试环境,可以显著缩短测试时间。
测试用例总数:用例数越多,覆盖范围越广
通过率必须达到95%以上
http://www.lryc.cn/news/620451.html

相关文章:

  • toRefs、storeToRefs实际应用
  • 图书商城小程序怎么做?实体书店如何在微信小程序上卖书?
  • 机器学习 - Kaggle项目实践(3)Digit Recognizer 手写数字识别
  • 20道HTML相关前端面试题及答案
  • 如何通过WiFi将文件从安卓设备传输到电脑
  • 点图:数据分布的可视化利器
  • PostgreSQL——视图
  • 读书笔记:《我看见的世界》
  • 为什么Integer缓存-128 ~ 127
  • 【Linux学习|黑马笔记|Day4】IP地址、主机名、网络请求、下载、端口、进程管理、主机状态监控、环境变量、文件的上传和下载、压缩和解压
  • 编排之神-Kubernetes微服务专题--ingress-nginx及金丝雀Canary的演练
  • [Oracle数据库] ORACLE基本DML操作
  • 图论Day2学习心得
  • Pytest本地插件定制及发布指南
  • 代码随想录Day50:图论(图论理论、深度搜索理论、所有可达路径、广度搜索理论)
  • python sqlite3模块
  • 高效解决 pip install 报错 SSLError: EOF occurred in violation of protocol
  • 《贵州棒球百科》体育赛事排名·棒球1号位
  • 视频号主页的企业信息如何设置?
  • 消费级显卡分布式智能体协同:构建高性价比医疗AI互动智能体的理论与实践路径
  • 从理论到落地:分布式事务全解析(原理 + 方案 + 避坑指南)
  • 云原生存储架构设计与性能优化
  • 【java实现一个接口多个实现类通用策略模式】
  • GitHub 仓库代码上传指南
  • Python包性能优化与并发编程:构建高性能应用的核心技术(续)
  • OpenBMC中C++策略模式架构、原理与应用
  • Qt基本控件
  • Elasticsearch:如何使用 Qwen3 来做向量搜索
  • 设计模式-策略模式 Java
  • 设计模式基础概念(行为模式):策略模式