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

目录

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.pswp.cn/web/93128.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/93128.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mysql数据库迁移到GaussDB注意事项

mysql数据库迁移高斯数据库 建议开启高斯数据库M模式,mysql兼容模式,可以直接使用mysql的建表语句,自增主键可以使用AUTO_INCREMENT,如果不开启M模式,只能使用高斯数据库的序列添加自增主键1:如果使用数据库…

苹果正计划大举进军人工智能硬件领域

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

Serverless 架构核心解析与应用实践

Serverless 的核心定义与优势‌‌核心定义Serverless(无服务器架构)是一种云计算模型,开发者无需关注底层服务器管理,由云服务商自动分配资源、弹性扩缩容,并按实际使用量计费‌。其核心特点包括:‌按需计算…

Redis持久化机制详解:RDB与AOF的全面对比与实践指南

目录 一、RDB持久化机制 1.1 RDB概述 1.2 RDB触发机制 1) 手动执行save命令 2) 手动执行bgsave命令 3) Redis正常关闭时 4) 自动触发条件满足时 1.3 RDB详细配置 1.4 RDB实现原理 1.5 RDB的优缺点分析 二、AOF持久化机制 2.1 AOF概述 2.2 AOF工作流程 2.3 AOF同步…

介绍一下jQuery的AJAX异步请求

目录 一、核心方法:$.ajax() 二、简化方法(常用场景) 1. $.get():快速发送 GET 请求(获取数据) 2. $.post():快速发送 POST 请求(提交数据) 3. $.getJSON()&#xf…

Win10系统Ruby+Devkit3.4.5-1安装

Win10系统RubyDevkit3.4.5-1安装安装步骤软件工具安装Ruby安装gem mysql2处理libmysql.dll验证mysql2安装步骤 软件工具 mysql-connector-c-6.1.11-winx64.zip rubyinstaller-devkit-3.4.5-1-x64.exe 安装Ruby 执行rubyinstaller-devkit-3.4.5-1-x64.exe,期间可…

社交工程:洞穿人心防线的无形之矛

在网络安全领域,一道无形的裂痕正在迅速蔓延。它不是复杂的零日漏洞,也不是精妙的恶意代码,而是利用人性弱点进行攻击的古老技艺——社交工程。当全球网络安全支出突破千亿美元大关,防火墙筑得越来越高,加密算法越来越…

Go 并发控制利器 ants 使用文档

https://github.com/panjf2000/ants1.1 什么是 ants ants 是一个高性能的 Go 语言 goroutine 池,它能复用已完成任务的 goroutine,避免频繁创建和销毁 goroutine,节省 CPU 与内存开销,并且能限制并发数量防止资源被耗尽。 1.2 安装…

Day57--图论--53. 寻宝(卡码网)

Day57–图论–53. 寻宝(卡码网) 今天学习:最小生成树。有两种算法(Prim和Kruskal)和一道例题。 prim 算法是维护节点的集合,而 Kruskal 是维护边的集合。 最小生成树:所有节点的最小连通子图&am…

解决海洋探测数据同步网络问题的新思路——基于智能组网技术的探索

随着海洋探测技术的不断发展,数据同步网络的稳定性和低延迟需求变得愈发重要。海洋探测数据来自多个分布式采集点,这些点需要高效的组网方式来实现实时数据传输。然而,由于海洋环境的特殊性(如复杂的网络拓扑、高湿度和极端温度&a…

设计模式笔记_行为型_责任链模式

1. 责任链模式介绍责任链模式(Chain of Responsibility)是一种行为设计模式,它允许将多个处理器(处理对象)连接成一条链,并沿着这条链传递请求,直到有一个处理器处理它为止。职责链模式的主要目…

pygame的帧处理中,涉及键盘的有`pg.event.get()`与`pg.key.get_pressed()` ,二者有什么区别与联系?

一、pg.event.get() 返回的是一组事件 pg.event.get() 返回的是一组事件(一个包含多个事件对象的列表)。这是因为在游戏的“一帧”时间内(通常1/60秒左右),用户可能会触发多个事件(比如同时按下多个键、快速…

TF - IDF算法面试与工作常见问题全解析

在自然语言处理领域,TF - IDF算法是一个基础且重要的概念。无论是在求职面试还是在实际工作中,都经常会遇到与TF - IDF相关的问题。以下是一些常见的问题及其详细解答: 一、基本概念类问题 1. 什么是TF - IDF算法? TF - IDF&#…

Transformer网络结构解析

博主会经常分享自己在人工智能阶段的学习笔记,欢迎大家访问我滴个人博客!(都不白来!) 小牛壮士 - 个人博客https://kukudelin.top/ 前言 Transformer 广泛应用于自然语言处理(如机器翻译、文本生成&…

gateway进行接口日志打印

打印需求:对所有的接口打印:请求方式,请求路径,请求参数,用户id,访问IP,访问时间对增删改操作的接口打印:接口响应打印方案:给GET设置一个白名单(因为get请求…

MATLAB实现图像增强(直方图均衡化)

直方图均衡化是一种常用的图像增强技术,它通过重新分布图像的像素强度值来增强图像的对比度。以下是MATLAB中实现直方图均衡化的详细方法。%% 直方图均衡变换 clc;close all;clear all;warning off;%清除变量 rand(seed, 100); randn(seed, 100); format long g;%% …

java15学习笔记-密封类

360:Sealed Classes (Preview) 封闭类(预览) 总结 使用密封类和接口增强Java编程语言。密封类和接口限制了哪些其他类或接口可以扩展或实现它们。这是JDK 15中的预览语言功能。 目标 允许类或接口的作者控制负责实现它的代码。 提供一种比访问…

西门子PLC通过稳联技术EtherCAT转Profinet网关连接baumuller伺服器的配置案例

西门子PLC用稳联技术的EtherCAT转Profinet网关,连上baumuller伺服器的配置例子本案例实现西门子S71200 PLC通过EtherCAT转Profinet网关对baumuller(Baumller)伺服器的实时控制,适用于高精度运动控制场景(如精密机床、自…

Ansible 详细笔记

Ansible 详细笔记 一、Ansible 基础概述 1.1 定义与定位 Ansible 是由 Red Hat 主导开发的开源自动化运维工具,基于 Python 语言实现,专注于简化 IT 基础设施的配置管理、应用部署、任务编排等操作。它采用无代理架构,通过 SSH 协议与被控节点…

【Java 后端】Spring Boot 集成 JPA 全攻略

Spring Boot 集成 JPA 全攻略 一、前言 在 Java Web 开发中,数据库访问是绕不开的话题。 传统方式使用 JDBC 编写 SQL,维护困难、可读性差。后来有了 MyBatis 这种半自动 ORM 框架,再到 JPA(Java Persistence API)这…